dsh-biocore 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/lib/core-worker.js +268 -0
- package/lib/index.js +160 -70
- package/lib/modules/autonomy.js +125 -0
- package/lib/modules/cache-stats.js +48 -0
- package/lib/modules/core-turn.js +24 -6
- package/lib/modules/expression-inject.js +0 -10
- package/lib/modules/expression-refresh.js +0 -9
- package/lib/modules/memory-capture.js +2 -1
- package/lib/modules/memory-inject.js +50 -5
- package/lib/modules/memory-tools.js +7 -4
- package/lib/modules/python-env.js +46 -0
- package/lib/modules/safety-gate.js +0 -3
- package/lib/modules/series.js +54 -0
- package/lib/modules/switch.js +16 -0
- package/lib/py-bridge.js +81 -15
- package/package.json +1 -1
- package/python/biocore_memory/core/config.py +138 -127
- package/python/biocore_memory/engine.py +2653 -2476
- package/python/biocore_memory/recall/embedding.py +129 -0
- package/python/biocore_memory/recall/feedback.py +202 -0
- package/python/biocore_model/preference_drift.py +255 -0
- package/python/biocore_model/tests/__init__.py +1 -0
- package/python/biocore_model/tests/core_mode_ui_check.mjs +63 -0
- package/python/biocore_model/tests/core_status_check.mjs +29 -0
- package/python/biocore_model/tests/dom_safe_check.mjs +22 -0
- package/python/biocore_model/tests/leak_samples_run.py +18 -0
- package/python/biocore_model/tests/orb_ring_check.mjs +20 -0
- package/python/biocore_model/tests/panel_html_check.mjs +63 -0
- package/python/biocore_model/tests/persona_flow_check.mjs +17 -0
- package/python/biocore_model/tests/render_guard_check.mjs +19 -0
- package/python/biocore_model/tests/switch_groups_check.mjs +94 -0
- package/python/biocore_model/tests/tabs_state_check.mjs +24 -0
- package/python/biocore_model/tests/test_affective_labor.py +102 -0
- package/python/biocore_model/tests/test_appraisal.py +137 -0
- package/python/biocore_model/tests/test_appraisal_action.py +55 -0
- package/python/biocore_model/tests/test_arch_convergence.py +35 -0
- package/python/biocore_model/tests/test_attachment_behavior.py +95 -0
- package/python/biocore_model/tests/test_attachment_regulation.py +63 -0
- package/python/biocore_model/tests/test_attachment_state.py +29 -0
- package/python/biocore_model/tests/test_attribution.py +56 -0
- package/python/biocore_model/tests/test_attributor.py +108 -0
- package/python/biocore_model/tests/test_autonomic.py +69 -0
- package/python/biocore_model/tests/test_autonomy_accumulator.py +84 -0
- package/python/biocore_model/tests/test_autonomy_affect.py +62 -0
- package/python/biocore_model/tests/test_autonomy_blame.py +44 -0
- package/python/biocore_model/tests/test_autonomy_caps.py +29 -0
- package/python/biocore_model/tests/test_autonomy_check.py +40 -0
- package/python/biocore_model/tests/test_autonomy_clingy.py +41 -0
- package/python/biocore_model/tests/test_autonomy_cooldown_selfcontact.py +138 -0
- package/python/biocore_model/tests/test_autonomy_frustration.py +42 -0
- package/python/biocore_model/tests/test_autonomy_hourly_limit.py +46 -0
- package/python/biocore_model/tests/test_autonomy_loop.py +64 -0
- package/python/biocore_model/tests/test_autonomy_mode.py +37 -0
- package/python/biocore_model/tests/test_autonomy_model.py +60 -0
- package/python/biocore_model/tests/test_autonomy_model_v2.py +68 -0
- package/python/biocore_model/tests/test_autonomy_module_text.py +104 -0
- package/python/biocore_model/tests/test_autonomy_params.py +163 -0
- package/python/biocore_model/tests/test_autonomy_reason.py +34 -0
- package/python/biocore_model/tests/test_autonomy_self.py +153 -0
- package/python/biocore_model/tests/test_autonomy_send_gate.py +38 -0
- package/python/biocore_model/tests/test_autonomy_silence.py +37 -0
- package/python/biocore_model/tests/test_autonomy_status.py +35 -0
- package/python/biocore_model/tests/test_autonomy_test_trigger.py +24 -0
- package/python/biocore_model/tests/test_autonomy_tick.py +148 -0
- package/python/biocore_model/tests/test_autonomy_v4.py +64 -0
- package/python/biocore_model/tests/test_autonomy_v5.py +43 -0
- package/python/biocore_model/tests/test_autonomy_v7.py +48 -0
- package/python/biocore_model/tests/test_autonomy_v_theta.py +85 -0
- package/python/biocore_model/tests/test_batch1_wiring.py +231 -0
- package/python/biocore_model/tests/test_batch2_wiring.py +231 -0
- package/python/biocore_model/tests/test_batch3_wiring.py +223 -0
- package/python/biocore_model/tests/test_bio_tick.py +202 -0
- package/python/biocore_model/tests/test_boundary.py +183 -0
- package/python/biocore_model/tests/test_boundary_gate.py +295 -0
- package/python/biocore_model/tests/test_brain_split.py +99 -0
- package/python/biocore_model/tests/test_co_recall.py +350 -0
- package/python/biocore_model/tests/test_commitment_extractor.py +197 -0
- package/python/biocore_model/tests/test_compassion_fatigue.py +84 -0
- package/python/biocore_model/tests/test_connection_bids.py +69 -0
- package/python/biocore_model/tests/test_core_bridge.py +88 -0
- package/python/biocore_model/tests/test_core_bridge_episode.py +14 -0
- package/python/biocore_model/tests/test_core_bridge_forget.py +17 -0
- package/python/biocore_model/tests/test_core_bridge_identity.py +15 -0
- package/python/biocore_model/tests/test_core_bridge_media.py +17 -0
- package/python/biocore_model/tests/test_core_bridge_relation.py +17 -0
- package/python/biocore_model/tests/test_core_bridge_summary.py +17 -0
- package/python/biocore_model/tests/test_core_client.py +156 -0
- package/python/biocore_model/tests/test_core_commands.py +146 -0
- package/python/biocore_model/tests/test_core_mode.py +161 -0
- package/python/biocore_model/tests/test_core_state.py +240 -0
- package/python/biocore_model/tests/test_criticality.py +61 -0
- package/python/biocore_model/tests/test_cross_session_copy.py +67 -0
- package/python/biocore_model/tests/test_debug_values.py +169 -0
- package/python/biocore_model/tests/test_defense_mechanisms.py +177 -0
- package/python/biocore_model/tests/test_delta_budget.py +157 -0
- package/python/biocore_model/tests/test_diag_trace.py +201 -0
- package/python/biocore_model/tests/test_directives_compat.py +698 -0
- package/python/biocore_model/tests/test_dissonance.py +97 -0
- package/python/biocore_model/tests/test_drift_sign.py +87 -0
- package/python/biocore_model/tests/test_embedding_fallback.py +277 -0
- package/python/biocore_model/tests/test_embedding_local.py +279 -0
- package/python/biocore_model/tests/test_embedding_mode.py +175 -0
- package/python/biocore_model/tests/test_embedding_modes.py +342 -0
- package/python/biocore_model/tests/test_emotion_geometry.py +114 -0
- package/python/biocore_model/tests/test_emotion_probe.py +214 -0
- package/python/biocore_model/tests/test_emotion_probe_concurrency.py +128 -0
- package/python/biocore_model/tests/test_entity_graph.py +198 -0
- package/python/biocore_model/tests/test_entity_resolve.py +446 -0
- package/python/biocore_model/tests/test_envy.py +173 -0
- package/python/biocore_model/tests/test_exit_or_silence.py +209 -0
- package/python/biocore_model/tests/test_expr_guard.py +1 -0
- package/python/biocore_model/tests/test_expr_intent.py +66 -0
- package/python/biocore_model/tests/test_expr_state.py +66 -0
- package/python/biocore_model/tests/test_expr_template.py +71 -0
- package/python/biocore_model/tests/test_expr_tone.py +41 -0
- package/python/biocore_model/tests/test_extreme_scenarios.py +147 -0
- package/python/biocore_model/tests/test_feedback_flow.py +185 -0
- package/python/biocore_model/tests/test_feedback_guard.py +65 -0
- package/python/biocore_model/tests/test_feedback_llm.py +59 -0
- package/python/biocore_model/tests/test_feedback_llm_inferred.py +140 -0
- package/python/biocore_model/tests/test_feedback_memory.py +63 -0
- package/python/biocore_model/tests/test_feedback_user.py +61 -0
- package/python/biocore_model/tests/test_frustration_curve.py +66 -0
- package/python/biocore_model/tests/test_global_bio_state.py +116 -0
- package/python/biocore_model/tests/test_governance.py +181 -0
- package/python/biocore_model/tests/test_granularity.py +133 -0
- package/python/biocore_model/tests/test_graph_extract.py +224 -0
- package/python/biocore_model/tests/test_graph_recall.py +226 -0
- package/python/biocore_model/tests/test_graph_sqlite.py +298 -0
- package/python/biocore_model/tests/test_growth.py +301 -0
- package/python/biocore_model/tests/test_intent_render.py +60 -0
- package/python/biocore_model/tests/test_intero_regulation.py +224 -0
- package/python/biocore_model/tests/test_intero_style.py +146 -0
- package/python/biocore_model/tests/test_item3_numeric.py +105 -0
- package/python/biocore_model/tests/test_item6_numeric.py +116 -0
- package/python/biocore_model/tests/test_layered_memory.py +174 -0
- package/python/biocore_model/tests/test_leak_guard.py +75 -0
- package/python/biocore_model/tests/test_learning_probe.py +93 -0
- package/python/biocore_model/tests/test_life_loop.py +222 -0
- package/python/biocore_model/tests/test_life_loop_10s.py +131 -0
- package/python/biocore_model/tests/test_local_embedding_health.py +258 -0
- package/python/biocore_model/tests/test_memory_affect_encoding.py +79 -0
- package/python/biocore_model/tests/test_memory_backend.py +332 -0
- package/python/biocore_model/tests/test_memory_dedup.py +316 -0
- package/python/biocore_model/tests/test_memory_dual.py +219 -0
- package/python/biocore_model/tests/test_memory_dual_wiring.py +219 -0
- package/python/biocore_model/tests/test_memory_feedback.py +241 -0
- package/python/biocore_model/tests/test_memory_fixes.py +84 -0
- package/python/biocore_model/tests/test_memory_forgetting.py +58 -0
- package/python/biocore_model/tests/test_memory_inject.py +211 -0
- package/python/biocore_model/tests/test_memory_mood_recall.py +118 -0
- package/python/biocore_model/tests/test_memory_store.py +372 -0
- package/python/biocore_model/tests/test_metacog.py +103 -0
- package/python/biocore_model/tests/test_migrate.py +245 -0
- package/python/biocore_model/tests/test_migrate_store.py +194 -0
- package/python/biocore_model/tests/test_mind_wander.py +65 -0
- package/python/biocore_model/tests/test_mood.py +35 -0
- package/python/biocore_model/tests/test_narrative.py +114 -0
- package/python/biocore_model/tests/test_needs_esteem.py +51 -0
- package/python/biocore_model/tests/test_needs_events.py +205 -0
- package/python/biocore_model/tests/test_needs_sleep.py +54 -0
- package/python/biocore_model/tests/test_noise_bank.py +54 -0
- package/python/biocore_model/tests/test_numeric_schema.py +98 -0
- package/python/biocore_model/tests/test_output_protocol.py +65 -0
- package/python/biocore_model/tests/test_page_debug.py +147 -0
- package/python/biocore_model/tests/test_page_status_autonomy.py +215 -0
- package/python/biocore_model/tests/test_pipeline.py +345 -0
- package/python/biocore_model/tests/test_pipeline_api.py +192 -0
- package/python/biocore_model/tests/test_pipeline_mode.py +222 -0
- package/python/biocore_model/tests/test_plugin_config.py +85 -0
- package/python/biocore_model/tests/test_policy_learning.py +116 -0
- package/python/biocore_model/tests/test_possible_selves.py +88 -0
- package/python/biocore_model/tests/test_predictor.py +227 -0
- package/python/biocore_model/tests/test_preference_drift.py +142 -0
- package/python/biocore_model/tests/test_preset.py +142 -0
- package/python/biocore_model/tests/test_proactive_context.py +70 -0
- package/python/biocore_model/tests/test_proactive_history.py +90 -0
- package/python/biocore_model/tests/test_proactive_memory.py +141 -0
- package/python/biocore_model/tests/test_proactive_send_record.py +88 -0
- package/python/biocore_model/tests/test_probe_contract.py +71 -0
- package/python/biocore_model/tests/test_prospective.py +154 -0
- package/python/biocore_model/tests/test_provider_fallback.py +89 -0
- package/python/biocore_model/tests/test_reconstruction.py +111 -0
- package/python/biocore_model/tests/test_regulation_strategies.py +228 -0
- package/python/biocore_model/tests/test_relation_stage.py +114 -0
- package/python/biocore_model/tests/test_relationship_stage.py +59 -0
- package/python/biocore_model/tests/test_release_140.py +55 -0
- package/python/biocore_model/tests/test_repair_after_boundary.py +249 -0
- package/python/biocore_model/tests/test_reply_limit.py +86 -0
- package/python/biocore_model/tests/test_reply_limit_persist.py +48 -0
- package/python/biocore_model/tests/test_reply_split.py +52 -0
- package/python/biocore_model/tests/test_reply_split_astrbot.py +66 -0
- package/python/biocore_model/tests/test_reply_split_inject.py +53 -0
- package/python/biocore_model/tests/test_rerank.py +229 -0
- package/python/biocore_model/tests/test_rollback.py +175 -0
- package/python/biocore_model/tests/test_runtime.py +175 -0
- package/python/biocore_model/tests/test_runtime_active.py +205 -0
- package/python/biocore_model/tests/test_runtime_pipeline.py +1583 -0
- package/python/biocore_model/tests/test_runtime_proactive.py +231 -0
- package/python/biocore_model/tests/test_scheduler.py +79 -0
- package/python/biocore_model/tests/test_secrecy.py +105 -0
- package/python/biocore_model/tests/test_self_esteem.py +45 -0
- package/python/biocore_model/tests/test_self_intent.py +347 -0
- package/python/biocore_model/tests/test_self_interest.py +183 -0
- package/python/biocore_model/tests/test_self_layer_wiring.py +198 -0
- package/python/biocore_model/tests/test_self_verification.py +113 -0
- package/python/biocore_model/tests/test_situation.py +142 -0
- package/python/biocore_model/tests/test_sleep_circadian.py +41 -0
- package/python/biocore_model/tests/test_social_pain.py +49 -0
- package/python/biocore_model/tests/test_social_repair.py +67 -0
- package/python/biocore_model/tests/test_social_theory.py +78 -0
- package/python/biocore_model/tests/test_source_monitoring.py +204 -0
- package/python/biocore_model/tests/test_speak_like_group.py +36 -0
- package/python/biocore_model/tests/test_stages.py +226 -0
- package/python/biocore_model/tests/test_state_tag.py +86 -0
- package/python/biocore_model/tests/test_template_expr.py +65 -0
- package/python/biocore_model/tests/test_text_inference.py +70 -0
- package/python/biocore_model/tests/test_theta_params.py +115 -0
- package/python/biocore_model/tests/test_topic_control.py +200 -0
- package/python/biocore_model/tests/test_turn_memory.py +105 -0
- package/python/biocore_model/tests/test_turn_memory_panel.py +174 -0
- package/python/biocore_model/tests/test_ui_settings.py +84 -0
- package/python/biocore_model/tests/test_ui_settings_authority.py +179 -0
- package/python/biocore_model/tests/test_user_appraisal_async.py +155 -0
- package/python/biocore_model/tests/test_user_classify.py +122 -0
- package/python/biocore_model/tests/test_value_order.py +117 -0
- package/python/biocore_model/tests/test_voice_bank.py +105 -0
- package/python/biocore_model/tests/test_waiting_sense.py +216 -0
- package/python/biocore_model/tests/test_x_directives.py +101 -0
- package/python/biocore_model/tests/ui_compat_check.mjs +22 -0
- package/python/biocore_model/tests/view_mode_check.mjs +17 -0
- package/python/biocore_runtime/autonomy.py +208 -0
- package/python/biocore_runtime/core_state.py +302 -0
- package/python/biocore_runtime/governance.py +495 -0
- package/python/biocore_runtime/growth.py +528 -0
- package/python/biocore_runtime/pipeline.py +850 -23
- package/python/biocore_runtime/relationship_state.py +214 -0
- package/python/biocore_runtime/self_layer.py +103 -0
- package/python/biocore_runtime/service.py +162 -0
- package/python/biocore_runtime/standby.py +472 -0
- package/ui/panel-layout.js +83 -39
- package/ui/panel.css +885 -743
- package/ui/panel.html +356 -296
- package/ui/panel.js +739 -865
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import unittest
|
|
3
|
+
from biocore_model import needs_esteem as ne
|
|
4
|
+
|
|
5
|
+
class NeedsTest(unittest.TestCase):
|
|
6
|
+
def test_defaults(self):
|
|
7
|
+
self.assertEqual(ne.defaults()['belonging'], 0.6)
|
|
8
|
+
def test_silence_belonging(self):
|
|
9
|
+
self.assertAlmostEqual(ne.silence_effects(10)['belonging'], -0.2)
|
|
10
|
+
def test_belonging_cap(self):
|
|
11
|
+
self.assertAlmostEqual(ne.silence_effects(100)['belonging'], -0.4)
|
|
12
|
+
def test_meaning_after_48h(self):
|
|
13
|
+
self.assertEqual(ne.silence_effects(24)['meaningful_existence'], 0.0)
|
|
14
|
+
self.assertLess(ne.silence_effects(60)['meaningful_existence'], 0.0)
|
|
15
|
+
def test_control_when_stalled(self):
|
|
16
|
+
self.assertLess(ne.silence_effects(10, phases_stalled=True)['control'], 0.0)
|
|
17
|
+
def test_responded_repairs_all(self):
|
|
18
|
+
needs = ne.on_responded(ne.defaults())
|
|
19
|
+
for key in ne.NEED_KEYS:
|
|
20
|
+
self.assertGreater(needs[key], 0.6)
|
|
21
|
+
def test_ignored_lowers_esteem(self):
|
|
22
|
+
self.assertLess(ne.on_ignored(ne.defaults())['self_esteem'], 0.6)
|
|
23
|
+
def test_rejected_bigger_drop(self):
|
|
24
|
+
self.assertLess(ne.on_ignored(ne.defaults(), rejected=True)['self_esteem'], ne.on_ignored(ne.defaults())['self_esteem'])
|
|
25
|
+
def test_praised_raises_esteem(self):
|
|
26
|
+
self.assertGreater(ne.on_praised(ne.defaults())['self_esteem'], 0.6)
|
|
27
|
+
def test_dominant(self):
|
|
28
|
+
needs = ne.defaults(); needs['meaningful_existence'] = 0.2
|
|
29
|
+
self.assertEqual(ne.dominant(needs), 'meaningful_existence')
|
|
30
|
+
def test_dominant_empty_when_full(self):
|
|
31
|
+
needs = {key: 0.9 for key in ne.NEED_KEYS}
|
|
32
|
+
self.assertEqual(ne.dominant(needs), '')
|
|
33
|
+
def test_four_behaviours_differ(self):
|
|
34
|
+
tones = set()
|
|
35
|
+
for key in ne.NEED_KEYS:
|
|
36
|
+
needs = ne.defaults(); needs[key] = 0.2
|
|
37
|
+
b = ne.behaviour(needs); tones.add(b['tone'])
|
|
38
|
+
self.assertEqual(len(tones), 4)
|
|
39
|
+
def test_self_esteem_regress(self):
|
|
40
|
+
self.assertGreater(ne.self_esteem_update(0.2), 0.2)
|
|
41
|
+
self.assertLess(ne.self_esteem_update(0.9), 0.9)
|
|
42
|
+
def test_esteem_respond_boost(self):
|
|
43
|
+
self.assertGreater(ne.self_esteem_update(0.5, responded=True), 0.5)
|
|
44
|
+
def test_confidence_lower_with_low_esteem(self):
|
|
45
|
+
self.assertLess(ne.confidence_from_esteem(0.2), ne.confidence_from_esteem(0.55))
|
|
46
|
+
def test_clamp(self):
|
|
47
|
+
self.assertLessEqual(ne.self_esteem_update(1.0, praised=True), 1.0)
|
|
48
|
+
self.assertGreaterEqual(ne.self_esteem_update(0.0, ignored=True), 0.0)
|
|
49
|
+
|
|
50
|
+
if __name__ == '__main__':
|
|
51
|
+
unittest.main()
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""P1-3 need-event hook tests (needs_events.py + main wiring)."""
|
|
3
|
+
import ast
|
|
4
|
+
import json
|
|
5
|
+
import types
|
|
6
|
+
import unittest
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from biocore_model import needs_events as ne
|
|
10
|
+
|
|
11
|
+
ROOT = Path(__file__).resolve().parents[2]
|
|
12
|
+
MAIN = ROOT / "main.py"
|
|
13
|
+
SOURCE = MAIN.read_text(encoding="utf-8")
|
|
14
|
+
SCHEMA = json.loads((ROOT / "_conf_schema.json").read_text(encoding="utf-8"))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _load_helper():
|
|
18
|
+
tree = ast.parse(SOURCE)
|
|
19
|
+
node = next(item for item in tree.body
|
|
20
|
+
if isinstance(item, ast.FunctionDef)
|
|
21
|
+
and item.name == "_apply_needs_events")
|
|
22
|
+
clone = ast.FunctionDef(name=node.name, args=node.args, body=node.body,
|
|
23
|
+
decorator_list=[], returns=node.returns,
|
|
24
|
+
type_comment=None, lineno=node.lineno,
|
|
25
|
+
col_offset=node.col_offset,
|
|
26
|
+
end_lineno=node.end_lineno,
|
|
27
|
+
end_col_offset=node.end_col_offset)
|
|
28
|
+
log = []
|
|
29
|
+
ns = {"logger": types.SimpleNamespace(
|
|
30
|
+
info=lambda *a, **k: log.append(a),
|
|
31
|
+
warning=lambda *a, **k: None,
|
|
32
|
+
debug=lambda *a, **k: None)}
|
|
33
|
+
exec(compile(ast.Module(body=[clone], type_ignores=[]),
|
|
34
|
+
"<_apply_needs_events>", "exec"), ns)
|
|
35
|
+
return ns[node.name], log
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
HELPER, LOG = _load_helper()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class FakeCore:
|
|
42
|
+
def __init__(self, needs=None):
|
|
43
|
+
self.needs = dict(needs or ne.DEFAULT_NEEDS)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class FakePlugin:
|
|
47
|
+
def __init__(self, enabled=True):
|
|
48
|
+
self.needs_events_enabled = enabled
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class NeedsStateTest(unittest.TestCase):
|
|
52
|
+
def test_defaults_and_clamps(self):
|
|
53
|
+
state = ne.needs_state({"belonging": 5, "control": -2})
|
|
54
|
+
self.assertEqual(state["belonging"], 1.0)
|
|
55
|
+
self.assertEqual(state["control"], 0.0)
|
|
56
|
+
self.assertEqual(state["self_esteem"], ne.BASELINE)
|
|
57
|
+
self.assertEqual(ne.needs_state(None), ne.DEFAULT_NEEDS)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class EventEffectTest(unittest.TestCase):
|
|
61
|
+
def _apply(self, kind, strength=1.0, needs=None):
|
|
62
|
+
needed = dict(needs or ne.DEFAULT_NEEDS)
|
|
63
|
+
return ne.apply(needed, [{"kind": kind, "strength": strength}])
|
|
64
|
+
|
|
65
|
+
def test_praised_raises_self_esteem(self):
|
|
66
|
+
after, report = self._apply("praised")
|
|
67
|
+
self.assertGreater(after["self_esteem"], ne.BASELINE)
|
|
68
|
+
self.assertGreater(after["belonging"], ne.BASELINE)
|
|
69
|
+
self.assertGreater(report["deltas"]["self_esteem"], 0)
|
|
70
|
+
|
|
71
|
+
def test_rejected_lowers_belonging_and_esteem(self):
|
|
72
|
+
after, report = self._apply("rejected")
|
|
73
|
+
self.assertLess(after["belonging"], ne.BASELINE)
|
|
74
|
+
self.assertLess(after["self_esteem"], ne.BASELINE)
|
|
75
|
+
self.assertLess(report["deltas"]["belonging"], 0)
|
|
76
|
+
|
|
77
|
+
def test_ignored_lowers_belonging_and_meaning(self):
|
|
78
|
+
after, _report = self._apply("ignored")
|
|
79
|
+
self.assertLess(after["belonging"], ne.BASELINE)
|
|
80
|
+
self.assertLess(after["meaningful_existence"], ne.BASELINE)
|
|
81
|
+
|
|
82
|
+
def test_single_event_is_capped(self):
|
|
83
|
+
after, report = self._apply("rejected", strength=1.0)
|
|
84
|
+
self.assertEqual(report["deltas"]["belonging"], -ne.CAPS["belonging"])
|
|
85
|
+
after2, report2 = ne.apply(ne.DEFAULT_NEEDS,
|
|
86
|
+
[{"kind": "rejected", "strength": 1.0}] * 5)
|
|
87
|
+
self.assertEqual(report2["deltas"]["belonging"], -ne.CAPS["belonging"])
|
|
88
|
+
self.assertGreaterEqual(after2["belonging"], 0.0)
|
|
89
|
+
|
|
90
|
+
def test_needs_stay_in_range(self):
|
|
91
|
+
low = {key: 0.01 for key in ne.NEED_KEYS}
|
|
92
|
+
after, _report = ne.apply(low, [{"kind": "abandoned",
|
|
93
|
+
"strength": 1.0}] * 4)
|
|
94
|
+
self.assertTrue(all(0.0 <= value <= 1.0 for value in after.values()))
|
|
95
|
+
self.assertEqual(after["belonging"], 0.0)
|
|
96
|
+
|
|
97
|
+
def test_zero_strength_is_noop(self):
|
|
98
|
+
after, report = ne.apply(ne.DEFAULT_NEEDS,
|
|
99
|
+
[{"kind": "praised", "strength": 0.0}])
|
|
100
|
+
self.assertEqual(after, ne.DEFAULT_NEEDS)
|
|
101
|
+
self.assertEqual(report["events"], [])
|
|
102
|
+
str_after, _ = ne.apply(ne.DEFAULT_NEEDS,
|
|
103
|
+
[{"kind": "praised", "strength": 0}])
|
|
104
|
+
self.assertEqual(str_after, ne.DEFAULT_NEEDS)
|
|
105
|
+
|
|
106
|
+
def test_apply_does_not_mutate_input(self):
|
|
107
|
+
source = dict(ne.DEFAULT_NEEDS)
|
|
108
|
+
before = dict(source)
|
|
109
|
+
ne.apply(source, [{"kind": "praised", "strength": 1.0}])
|
|
110
|
+
self.assertEqual(source, before)
|
|
111
|
+
|
|
112
|
+
def test_scale_factor(self):
|
|
113
|
+
_after, full = ne.apply(ne.DEFAULT_NEEDS, [{"kind": "praised"}])
|
|
114
|
+
_after2, half = ne.apply(ne.DEFAULT_NEEDS, [{"kind": "praised"}],
|
|
115
|
+
scale=0.5)
|
|
116
|
+
self.assertAlmostEqual(half["deltas"]["self_esteem"],
|
|
117
|
+
full["deltas"]["self_esteem"] / 2.0)
|
|
118
|
+
|
|
119
|
+
def test_invalid_events_are_safe(self):
|
|
120
|
+
after, report = ne.apply(None, "nope")
|
|
121
|
+
self.assertEqual(after, ne.DEFAULT_NEEDS)
|
|
122
|
+
self.assertEqual(report["events"], [])
|
|
123
|
+
after2, _ = ne.apply(ne.DEFAULT_NEEDS, [{"kind": "unknown"}])
|
|
124
|
+
self.assertEqual(after2, ne.DEFAULT_NEEDS)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class DimsEventsTest(unittest.TestCase):
|
|
128
|
+
def test_positive_dims(self):
|
|
129
|
+
kinds = {event["kind"]
|
|
130
|
+
for event in ne.events_from_dims({"goal": 0.8, "involve": 0.7})}
|
|
131
|
+
self.assertIn("achieved", kinds)
|
|
132
|
+
self.assertIn("connected", kinds)
|
|
133
|
+
|
|
134
|
+
def test_negative_dims(self):
|
|
135
|
+
kinds = {event["kind"]
|
|
136
|
+
for event in ne.events_from_dims({"hit": 0.9, "blame": 0.6})}
|
|
137
|
+
self.assertIn("rejected", kinds)
|
|
138
|
+
|
|
139
|
+
def test_neutral_dims_no_events(self):
|
|
140
|
+
self.assertEqual(ne.events_from_dims({"goal": 0.0, "hit": 0.0,
|
|
141
|
+
"involve": 0.1}), [])
|
|
142
|
+
self.assertEqual(ne.events_from_dims(None), [])
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class BehaviorChainTest(unittest.TestCase):
|
|
146
|
+
def test_deficit_biases_contact_and_clarify(self):
|
|
147
|
+
low = dict(ne.DEFAULT_NEEDS, belonging=0.2, control=0.25)
|
|
148
|
+
bias = ne.behavior_bias(low)
|
|
149
|
+
self.assertGreaterEqual(bias["seek_contact"], 0.6)
|
|
150
|
+
self.assertGreater(bias["ask_clarify"], 0.0)
|
|
151
|
+
|
|
152
|
+
def test_surplus_biases_sharing(self):
|
|
153
|
+
high = dict(ne.DEFAULT_NEEDS, meaningful_existence=0.95,
|
|
154
|
+
self_esteem=0.9)
|
|
155
|
+
bias = ne.behavior_bias(high)
|
|
156
|
+
self.assertGreater(bias["share_meaning"], 0.0)
|
|
157
|
+
self.assertGreater(bias["self_assert"], 0.0)
|
|
158
|
+
|
|
159
|
+
def test_event_to_needs_to_behavior_chain(self):
|
|
160
|
+
before = ne.behavior_bias(ne.DEFAULT_NEEDS)
|
|
161
|
+
dims = {"hit": 0.9, "blame": 0.7, "involve": 0.0}
|
|
162
|
+
events = ne.events_from_dims(dims)
|
|
163
|
+
after_needs, report = ne.apply(ne.DEFAULT_NEEDS, events)
|
|
164
|
+
after = ne.behavior_bias(after_needs)
|
|
165
|
+
self.assertGreater(after["seek_contact"], before["seek_contact"])
|
|
166
|
+
self.assertLess(after_needs["belonging"],
|
|
167
|
+
ne.DEFAULT_NEEDS["belonging"])
|
|
168
|
+
self.assertTrue(report["deltas"]["belonging"] < 0)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class WiringTest(unittest.TestCase):
|
|
172
|
+
def test_main_markers(self):
|
|
173
|
+
self.assertIn("def _apply_needs_events(plugin, core, dims)", SOURCE)
|
|
174
|
+
self.assertIn("_apply_needs_events(self, core, _inline.get(\"dims\"))",
|
|
175
|
+
SOURCE)
|
|
176
|
+
self.assertIn("needs_events as _needs_mod", SOURCE)
|
|
177
|
+
self.assertIn("needs_behavior", SOURCE)
|
|
178
|
+
self.assertIn("[biocore] needs events=", SOURCE)
|
|
179
|
+
|
|
180
|
+
def test_schema_default_on(self):
|
|
181
|
+
self.assertIn("needs_events_enabled", SCHEMA)
|
|
182
|
+
self.assertTrue(SCHEMA["needs_events_enabled"]["default"])
|
|
183
|
+
|
|
184
|
+
def test_helper_writes_needs_and_bias(self):
|
|
185
|
+
plugin = FakePlugin()
|
|
186
|
+
core = FakeCore()
|
|
187
|
+
LOG.clear()
|
|
188
|
+
report = HELPER(plugin, core, {"hit": 0.9, "blame": 0.7})
|
|
189
|
+
self.assertLess(core.needs["belonging"], ne.BASELINE)
|
|
190
|
+
self.assertIn("seek_contact", core.needs_behavior)
|
|
191
|
+
self.assertTrue(report["events"])
|
|
192
|
+
self.assertTrue(any("needs events" in str(row) for row in LOG))
|
|
193
|
+
|
|
194
|
+
def test_helper_disabled_and_neutral_are_noops(self):
|
|
195
|
+
core = FakeCore()
|
|
196
|
+
before = dict(core.needs)
|
|
197
|
+
HELPER(FakePlugin(enabled=False), core, {"hit": 0.9})
|
|
198
|
+
self.assertEqual(core.needs, before)
|
|
199
|
+
HELPER(FakePlugin(), core, {"hit": 0.0})
|
|
200
|
+
self.assertEqual(core.needs, before)
|
|
201
|
+
self.assertFalse(hasattr(core, "needs_behavior"))
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
if __name__ == "__main__":
|
|
205
|
+
unittest.main()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import unittest
|
|
3
|
+
from biocore_model import needs_sleep as ns, needs_esteem as ne
|
|
4
|
+
|
|
5
|
+
NOW = 1_700_000_000
|
|
6
|
+
|
|
7
|
+
class NeedsSleepTest(unittest.TestCase):
|
|
8
|
+
def test_four_needs_include_control_meaning(self):
|
|
9
|
+
data = ns.defaults(NOW)
|
|
10
|
+
for key in ('belonging', 'self_esteem', 'control', 'meaningful_existence'):
|
|
11
|
+
self.assertIn(key, data)
|
|
12
|
+
def test_control_drops_when_stalled(self):
|
|
13
|
+
data = ns.need_step(ns.defaults(NOW), {'silence_hours': 10, 'stalled': True}, dt=10, now=NOW + 1)
|
|
14
|
+
self.assertLess(data['control'], 0.6)
|
|
15
|
+
def test_meaning_drops_after_48h(self):
|
|
16
|
+
long_silence = ns.need_step(ns.defaults(NOW), {'silence_hours': 60}, dt=10, now=NOW + 1)
|
|
17
|
+
short = ns.need_step(ns.defaults(NOW), {'silence_hours': 24}, dt=10, now=NOW + 1)
|
|
18
|
+
self.assertLess(long_silence['meaningful_existence'], short['meaningful_existence'])
|
|
19
|
+
def test_responded_repairs_all(self):
|
|
20
|
+
data = ns.need_step(ns.defaults(NOW), {'responded': True}, dt=0, now=NOW + 1)
|
|
21
|
+
for key in ns.NEED_KEYS: self.assertGreater(data[key], 0.6)
|
|
22
|
+
def test_idempotent_same_now(self):
|
|
23
|
+
first = ns.need_step(ns.defaults(NOW), {'silence_hours': 10}, dt=10, now=NOW)
|
|
24
|
+
second = ns.need_step(first, {'silence_hours': 10}, dt=10, now=NOW)
|
|
25
|
+
self.assertEqual(first['belonging'], second['belonging'])
|
|
26
|
+
def test_sleep_debt_accumulates(self):
|
|
27
|
+
data = ns.sleep_debt_step(0.0, slept_hours=0, dt=8, now=NOW)
|
|
28
|
+
self.assertGreater(data['debt'], 0)
|
|
29
|
+
def test_sleep_repays(self):
|
|
30
|
+
data = ns.sleep_debt_step(4.0, slept_hours=6, dt=6, now=NOW)
|
|
31
|
+
self.assertLess(data['debt'], 4.0)
|
|
32
|
+
def test_sleep_debt_idempotent(self):
|
|
33
|
+
first = ns.sleep_debt_step(2.0, 0, 8, NOW)
|
|
34
|
+
second = ns.sleep_debt_step(first['debt'], 0, 8, NOW, state={'last_debt_ts': NOW})
|
|
35
|
+
self.assertAlmostEqual(second['debt'], first['debt'])
|
|
36
|
+
def test_energy_and_irritability(self):
|
|
37
|
+
self.assertGreater(ns.energy_penalty(12), 0); self.assertGreater(ns.irritability(12), 0)
|
|
38
|
+
def test_view_fields(self):
|
|
39
|
+
view = ns.view(ns.defaults(NOW), 5)
|
|
40
|
+
for key in ('needs', 'dominant', 'sleep_debt', 'energy_penalty', 'irritability'): self.assertIn(key, view)
|
|
41
|
+
def test_needs_esteem_delegates_apply_silence(self):
|
|
42
|
+
old = ne.apply_silence(ne.defaults(), 10)
|
|
43
|
+
new = ns.need_step(ns.defaults(NOW), {'silence_hours': 10}, dt=0, now=NOW + 1)
|
|
44
|
+
self.assertAlmostEqual(old['belonging'], new['belonging'], places=3)
|
|
45
|
+
def test_needs_esteem_delegates_responded(self):
|
|
46
|
+
old = ne.on_responded(ne.defaults())
|
|
47
|
+
self.assertGreater(old['control'], 0.6)
|
|
48
|
+
def test_needs_esteem_delegates_ignored(self):
|
|
49
|
+
old = ne.on_ignored(ne.defaults(), rejected=True)
|
|
50
|
+
new = ns.need_step(ns.defaults(NOW), {'ignored': True}, dt=0, now=NOW + 1)
|
|
51
|
+
self.assertAlmostEqual(old['self_esteem'], new['self_esteem'], places=3)
|
|
52
|
+
|
|
53
|
+
if __name__ == '__main__':
|
|
54
|
+
unittest.main()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""HUMAN-2 noise bank tests: reproducibility, channels, bursts."""
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
from biocore_model import noise_bank as nb
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DeterminismTest(unittest.TestCase):
|
|
9
|
+
def test_same_seed_tick_reproducible(self):
|
|
10
|
+
self.assertEqual(nb.value(11, 7, "chat"), nb.value(11, 7, "chat"))
|
|
11
|
+
|
|
12
|
+
def test_different_seed_differs(self):
|
|
13
|
+
self.assertNotEqual(nb.value(11, 7), nb.value(12, 7))
|
|
14
|
+
|
|
15
|
+
def test_sample_window_shape(self):
|
|
16
|
+
rows = nb.sample_window(3, 10, count=5, name="chat")
|
|
17
|
+
self.assertEqual(len(rows), 5)
|
|
18
|
+
self.assertEqual(rows, nb.sample_window(3, 10, count=5, name="chat"))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ChannelTest(unittest.TestCase):
|
|
22
|
+
def test_channel_noise_differs(self):
|
|
23
|
+
self.assertNotEqual(nb.channel(5, 9, "chat"), nb.channel(5, 9, "voice"))
|
|
24
|
+
|
|
25
|
+
def test_common_is_shared_and_bounded(self):
|
|
26
|
+
value = nb.common(5, 9)
|
|
27
|
+
self.assertLessEqual(abs(value), nb.CLAMP)
|
|
28
|
+
self.assertEqual(value, nb.common(5, 9))
|
|
29
|
+
|
|
30
|
+
def test_value_clamped(self):
|
|
31
|
+
for tick in range(200):
|
|
32
|
+
self.assertLessEqual(abs(nb.value(7, tick, "chat")["value"]), nb.CLAMP)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class BurstTest(unittest.TestCase):
|
|
36
|
+
def test_burst_reproducible(self):
|
|
37
|
+
self.assertEqual(nb.burst(9, 4), nb.burst(9, 4))
|
|
38
|
+
|
|
39
|
+
def test_burst_disabled_or_forced(self):
|
|
40
|
+
self.assertFalse(nb.burst(9, 4, p=0.0)["hit"])
|
|
41
|
+
self.assertTrue(nb.burst(9, 4, p=1.0)["hit"])
|
|
42
|
+
|
|
43
|
+
def test_burst_rare(self):
|
|
44
|
+
hits = sum(1 for tick in range(500) if nb.burst(3, tick)["hit"])
|
|
45
|
+
self.assertLessEqual(hits, 60)
|
|
46
|
+
|
|
47
|
+
def test_burst_value_bounds(self):
|
|
48
|
+
row = nb.burst(3, 0, p=1.0)
|
|
49
|
+
self.assertLessEqual(abs(row["value"]), nb.CLAMP)
|
|
50
|
+
self.assertGreaterEqual(row["magnitude"], 0.0)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
unittest.main()
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Numeric schema + legend injection tests."""
|
|
3
|
+
import types
|
|
4
|
+
import unittest
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from biocore_model import expr_template as et
|
|
8
|
+
from biocore_model import runtime as rt
|
|
9
|
+
from biocore_model import schema
|
|
10
|
+
|
|
11
|
+
DOC = Path(__file__).resolve().parents[2] / "docs" / "NUMERIC-SCHEMA.md"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SchemaTest(unittest.TestCase):
|
|
15
|
+
def test_groups_and_fields_present(self):
|
|
16
|
+
names = {field["name"] for field in schema.FIELDS}
|
|
17
|
+
for expected in ("v", "a", "d", "social", "trust", "stress",
|
|
18
|
+
"self_esteem", "social_pain", "tension", "warmth",
|
|
19
|
+
"heaviness", "clarity", "belonging", "control",
|
|
20
|
+
"meaningful_existence", "goal", "blame", "certainty",
|
|
21
|
+
"novelty", "involve", "hit", "theta", "tau_hours",
|
|
22
|
+
"cooldown_s", "daily_cap", "storage_s", "retrieval_s",
|
|
23
|
+
"access_count", "arousal_at_encoding"):
|
|
24
|
+
self.assertIn(expected, names)
|
|
25
|
+
|
|
26
|
+
def test_ranges(self):
|
|
27
|
+
ranges = {field["name"]: (field["min"], field["max"])
|
|
28
|
+
for field in schema.FIELDS}
|
|
29
|
+
self.assertEqual(ranges["v"], (-1.0, 1.0))
|
|
30
|
+
self.assertEqual(ranges["goal"], (-1.0, 1.0))
|
|
31
|
+
self.assertEqual(ranges["stress"], (0.0, 1.0))
|
|
32
|
+
self.assertEqual(ranges["theta"], (0.35, 0.75))
|
|
33
|
+
self.assertEqual(ranges["cooldown_s"], (1200, 10800))
|
|
34
|
+
|
|
35
|
+
def test_docs_match_code(self):
|
|
36
|
+
self.assertEqual(DOC.read_text(encoding="utf-8"), schema.render_markdown())
|
|
37
|
+
text = DOC.read_text(encoding="utf-8")
|
|
38
|
+
for field in schema.FIELDS:
|
|
39
|
+
self.assertIn(field["name"], text)
|
|
40
|
+
self.assertIn(field["desc"], text)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class LegendTest(unittest.TestCase):
|
|
44
|
+
def test_significant_values_appear(self):
|
|
45
|
+
text = schema.numeric_legend({"v": 0.55, "a": 0.9, "stress": 0.7})
|
|
46
|
+
self.assertIn(schema.LEGEND_HEADER, text)
|
|
47
|
+
self.assertIn("v 效价", text)
|
|
48
|
+
self.assertIn("a 唤醒", text)
|
|
49
|
+
self.assertIn("stress 压力", text)
|
|
50
|
+
self.assertLessEqual(len(text), schema.LEGEND_LIMIT)
|
|
51
|
+
|
|
52
|
+
def test_flat_state_is_empty(self):
|
|
53
|
+
text = schema.numeric_legend({"v": 0.0, "a": 0.4, "stress": 0.1,
|
|
54
|
+
"social": 0.55, "trust": 0.5})
|
|
55
|
+
self.assertEqual(text, "")
|
|
56
|
+
|
|
57
|
+
def test_threshold_and_custom_baseline(self):
|
|
58
|
+
self.assertEqual(schema.numeric_legend({"v": 0.14}), "")
|
|
59
|
+
self.assertNotEqual(schema.numeric_legend({"v": 0.2}), "")
|
|
60
|
+
self.assertEqual(schema.numeric_legend({"v": 0.9},
|
|
61
|
+
baselines={"v": 0.9}), "")
|
|
62
|
+
|
|
63
|
+
def test_max_five_items_and_limit(self):
|
|
64
|
+
values = {"v": 1.0, "a": 1.0, "stress": 1.0, "social": 1.0,
|
|
65
|
+
"trust": 1.0, "self_esteem": 1.0, "social_pain": 1.0}
|
|
66
|
+
text = schema.numeric_legend(values)
|
|
67
|
+
self.assertLessEqual(text.count("现在"), schema.LEGEND_MAX_ITEMS)
|
|
68
|
+
self.assertLessEqual(len(text), schema.LEGEND_LIMIT)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class InjectionTest(unittest.TestCase):
|
|
72
|
+
def test_legend_in_system_after_persona_before_protocol(self):
|
|
73
|
+
legend = schema.numeric_legend({"v": 0.8, "stress": 0.8})
|
|
74
|
+
out = et.build_reply(state_ctx={"numeric_legend": legend},
|
|
75
|
+
opts={"persona": "你是小灰", "speak_style": "口语"})
|
|
76
|
+
system = out["system"]
|
|
77
|
+
self.assertIn(schema.LEGEND_HEADER, system)
|
|
78
|
+
self.assertLess(system.index("小灰"), system.index(schema.LEGEND_HEADER))
|
|
79
|
+
self.assertNotIn("【输出格式", system)
|
|
80
|
+
self.assertNotIn(schema.LEGEND_HEADER, out["user"])
|
|
81
|
+
|
|
82
|
+
def test_no_legend_when_flat(self):
|
|
83
|
+
out = et.build_reply(opts={"persona": "你是小灰"})
|
|
84
|
+
self.assertNotIn(schema.LEGEND_HEADER, out["system"])
|
|
85
|
+
|
|
86
|
+
def test_runtime_enrich_sets_legend(self):
|
|
87
|
+
core = types.SimpleNamespace(snapshot=lambda: {
|
|
88
|
+
"v": 0.8, "a": 0.9, "stress": 0.8, "social": 0.2,
|
|
89
|
+
"trust": 0.2, "self_esteem": 0.2})
|
|
90
|
+
plugin = types.SimpleNamespace(_relationship_of=lambda user: {},
|
|
91
|
+
persona_text="", prompt_rules=None)
|
|
92
|
+
ctx = {"core": core, "plugin": plugin, "user": {}, "meta": {}}
|
|
93
|
+
out = rt.enrich_ctx(ctx)
|
|
94
|
+
self.assertIn(schema.LEGEND_HEADER, out.get("numeric_legend", ""))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
unittest.main()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Output-protocol tests: single constant, system placement, strong format."""
|
|
3
|
+
import unittest
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from biocore_model import expr_template as et
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ConstantTest(unittest.TestCase):
|
|
10
|
+
def test_header_example_and_constraints(self):
|
|
11
|
+
text = et.PROMPT_OUTPUT_PROTOCOL
|
|
12
|
+
self.assertIn("【输出格式 · 必须遵守】", text)
|
|
13
|
+
self.assertIn('<bc>{"e":"joy","i":0.6}</bc>', text)
|
|
14
|
+
self.assertIn("e 只能取", text)
|
|
15
|
+
self.assertIn("joy / trust / anxiety / sadness / anger / loneliness / neutral", text)
|
|
16
|
+
self.assertIn("i 是 0 到 1 的小数", text)
|
|
17
|
+
self.assertIn("不要包在代码块里", text)
|
|
18
|
+
|
|
19
|
+
def test_old_weak_sentence_is_gone(self):
|
|
20
|
+
self.assertNotIn("只输出正文 + 一个 <bc> 标签", et.PROMPT_OUTPUT_PROTOCOL)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TemplatePlacementTest(unittest.TestCase):
|
|
24
|
+
def test_reply_system_has_no_protocol(self):
|
|
25
|
+
out = et.build_reply(opts={"persona": "你是小灰", "speak_style": "口语"})
|
|
26
|
+
self.assertNotIn("【输出格式", out["system"])
|
|
27
|
+
self.assertNotIn("<bc>", out["system"])
|
|
28
|
+
self.assertNotIn("【输出格式", out["user"])
|
|
29
|
+
|
|
30
|
+
def test_proactive_system_has_no_protocol(self):
|
|
31
|
+
out = et.build_proactive(opts={"persona": "你是小灰"})
|
|
32
|
+
self.assertNotIn("【输出格式", out["system"])
|
|
33
|
+
self.assertNotIn("<bc>", out["system"])
|
|
34
|
+
self.assertNotIn("【输出格式", out["user"])
|
|
35
|
+
|
|
36
|
+
def test_protocol_absent_everywhere(self):
|
|
37
|
+
out = et.build_reply(opts={})
|
|
38
|
+
self.assertEqual(out["system"].count("【输出格式 · 必须遵守】"), 0)
|
|
39
|
+
|
|
40
|
+
def test_order_no_protocol_in_user(self):
|
|
41
|
+
out = et.build_reply(opts={})
|
|
42
|
+
self.assertNotIn("输出协议", out["order"])
|
|
43
|
+
|
|
44
|
+
def test_custom_protocol_is_ignored(self):
|
|
45
|
+
out = et.build_reply(opts={"output_protocol": "CUSTOM-PROTOCOL"})
|
|
46
|
+
self.assertNotIn("CUSTOM-PROTOCOL", out["system"])
|
|
47
|
+
self.assertNotIn("CUSTOM-PROTOCOL", out["user"])
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class WiringTest(unittest.TestCase):
|
|
51
|
+
def test_main_uses_constant_not_old_literal(self):
|
|
52
|
+
source = (Path(__file__).resolve().parents[2] / "main.py").read_text(encoding="utf-8")
|
|
53
|
+
self.assertNotIn('_etpl.PROMPT_OUTPUT_PROTOCOL', source)
|
|
54
|
+
self.assertNotIn('"output_protocol"', source)
|
|
55
|
+
|
|
56
|
+
def test_runtime_has_no_protocol_default(self):
|
|
57
|
+
from biocore_model import runtime
|
|
58
|
+
ctx = runtime.make_request_ctx(plugin=None, event=None, core=None, text="x")
|
|
59
|
+
ctx = runtime.enrich_ctx(ctx)
|
|
60
|
+
opts = (ctx.get("meta") or {}).get("template_opts") or {}
|
|
61
|
+
self.assertFalse(opts.get("output_protocol"))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
unittest.main()
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""page/debug tests: field completeness, real attribute reads, error -> null."""
|
|
3
|
+
import ast
|
|
4
|
+
import asyncio
|
|
5
|
+
import json
|
|
6
|
+
import tempfile
|
|
7
|
+
import types
|
|
8
|
+
import unittest
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from biocore_model import intero_core as ic
|
|
12
|
+
|
|
13
|
+
MAIN = Path(__file__).resolve().parents[2] / "main.py"
|
|
14
|
+
SOURCE = MAIN.read_text(encoding="utf-8")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _load_page_debug():
|
|
18
|
+
tree = ast.parse(SOURCE)
|
|
19
|
+
cls = next(n for n in tree.body
|
|
20
|
+
if isinstance(n, ast.ClassDef) and n.name == "BioCorePlugin")
|
|
21
|
+
node = next(f for f in cls.body
|
|
22
|
+
if isinstance(f, ast.AsyncFunctionDef) and f.name == "page_debug")
|
|
23
|
+
blocks = types.SimpleNamespace(
|
|
24
|
+
BLOCKS=[types.SimpleNamespace(id="bio_emotion"),
|
|
25
|
+
types.SimpleNamespace(id="bio_goal"),
|
|
26
|
+
types.SimpleNamespace(id="runtime")],
|
|
27
|
+
_enabled_map=lambda plugin: {"bio_emotion": True, "bio_goal": False, "runtime": True})
|
|
28
|
+
ns = {"_bc_blocks": blocks,
|
|
29
|
+
"PROFILE_PATH": Path("/data/persona_profile.json"),
|
|
30
|
+
"DATA_DIR": Path("/data")}
|
|
31
|
+
clone = ast.AsyncFunctionDef(name=node.name, args=node.args, body=node.body,
|
|
32
|
+
decorator_list=[], returns=node.returns,
|
|
33
|
+
type_comment=None, lineno=node.lineno,
|
|
34
|
+
col_offset=node.col_offset, end_lineno=node.end_lineno,
|
|
35
|
+
end_col_offset=node.end_col_offset)
|
|
36
|
+
exec(compile(ast.Module(body=[clone], type_ignores=[]), "<page_debug>", "exec"), ns)
|
|
37
|
+
return ns[node.name], ns
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
PAGE_DEBUG, NS = _load_page_debug()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class FakeCore:
|
|
44
|
+
def __init__(self):
|
|
45
|
+
self.intero = {"tension": 0.8, "warmth": 0.2, "heaviness": 0.5, "clarity": 0.4}
|
|
46
|
+
self.needs = {"belonging": 0.4, "control": 0.6}
|
|
47
|
+
self.pending_intent = {"action": "repair", "reason": "tension"}
|
|
48
|
+
self.last_bio_tick_ts = 1700000000.0
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class FakePlugin:
|
|
52
|
+
def __init__(self, tmp):
|
|
53
|
+
self.data_dir = str(tmp)
|
|
54
|
+
self.cores = {"s1": FakeCore()}
|
|
55
|
+
self._life_tick_n = 42
|
|
56
|
+
self._policy_pending = {"ctx": "expect|secure|day"}
|
|
57
|
+
|
|
58
|
+
def _load_policy_q(self):
|
|
59
|
+
return {"expect|secure|day": {"q": 0.1}, "protest|secure|day": {"q": 0.2}}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class PageDebugTest(unittest.IsolatedAsyncioTestCase):
|
|
63
|
+
def setUp(self):
|
|
64
|
+
self.tmp = tempfile.TemporaryDirectory()
|
|
65
|
+
self.plugin = FakePlugin(self.tmp.name)
|
|
66
|
+
from biocore_model import ui_settings
|
|
67
|
+
ui_settings.save(self.plugin, {"autonomy": {"accumulator": {
|
|
68
|
+
"V": 0.3, "theta": 0.55, "ticks_since_fire": 2}}})
|
|
69
|
+
|
|
70
|
+
def tearDown(self):
|
|
71
|
+
self.tmp.cleanup()
|
|
72
|
+
|
|
73
|
+
async def _payload(self):
|
|
74
|
+
return await PAGE_DEBUG(self.plugin)
|
|
75
|
+
|
|
76
|
+
async def test_all_fields_present_and_json_safe(self):
|
|
77
|
+
out = await self._payload()
|
|
78
|
+
self.assertEqual(out["status"], "ok")
|
|
79
|
+
data = out["data"]
|
|
80
|
+
for key in ("loop", "bio", "autonomy", "paths", "blocks", "pipeline", "policy"):
|
|
81
|
+
self.assertIn(key, data)
|
|
82
|
+
json.dumps(out) # must be serialisable
|
|
83
|
+
|
|
84
|
+
async def test_loop_reads_live_counter(self):
|
|
85
|
+
data = (await self._payload())["data"]
|
|
86
|
+
self.assertEqual(data["loop"]["count"], 42)
|
|
87
|
+
self.assertEqual(data["loop"]["interval_s"], 10)
|
|
88
|
+
self.assertEqual(data["loop"]["last_tick_ts"], 1700000000.0)
|
|
89
|
+
|
|
90
|
+
async def test_bio_reads_core(self):
|
|
91
|
+
data = (await self._payload())["data"]
|
|
92
|
+
expected = ic.discomfort(self.plugin.cores["s1"].intero)["total"]
|
|
93
|
+
self.assertAlmostEqual(data["bio"]["discomfort"], expected)
|
|
94
|
+
self.assertEqual(data["bio"]["pending_intent"]["action"], "repair")
|
|
95
|
+
self.assertEqual(data["bio"]["needs"]["belonging"], 0.4)
|
|
96
|
+
self.assertEqual(data["bio"]["intero"]["tension"], 0.8)
|
|
97
|
+
|
|
98
|
+
async def test_autonomy_accumulator(self):
|
|
99
|
+
data = (await self._payload())["data"]
|
|
100
|
+
self.assertEqual(data["autonomy"]["V"], 0.3)
|
|
101
|
+
self.assertEqual(data["autonomy"]["theta"], 0.55)
|
|
102
|
+
self.assertEqual(data["autonomy"]["ticks_since_fire"], 2)
|
|
103
|
+
|
|
104
|
+
async def test_paths_use_authority_settings_file(self):
|
|
105
|
+
data = (await self._payload())["data"]
|
|
106
|
+
self.assertTrue(data["paths"]["settings"].endswith("ui_settings.json"))
|
|
107
|
+
self.assertIn("persona_profile.json", data["paths"]["profile"])
|
|
108
|
+
self.assertTrue(data["paths"]["data_dir"])
|
|
109
|
+
|
|
110
|
+
async def test_blocks_and_pipeline_and_policy(self):
|
|
111
|
+
data = (await self._payload())["data"]
|
|
112
|
+
self.assertEqual(data["blocks"]["enabled"], ["bio_emotion", "runtime"])
|
|
113
|
+
self.assertEqual(data["blocks"]["skipped"], ["bio_goal"])
|
|
114
|
+
self.assertIn(data["pipeline"]["mode"], ("shadow", "active"))
|
|
115
|
+
self.assertIsInstance(data["pipeline"]["last_trace"], dict)
|
|
116
|
+
self.assertEqual(data["policy"]["q_size"], 2)
|
|
117
|
+
self.assertEqual(data["policy"]["last_ctx"], "expect|secure|day")
|
|
118
|
+
|
|
119
|
+
async def test_failures_degrade_to_null_not_crash(self):
|
|
120
|
+
plugin = FakePlugin(self.tmp.name)
|
|
121
|
+
plugin._life_tick_n = None
|
|
122
|
+
|
|
123
|
+
def boom(*args, **kwargs):
|
|
124
|
+
raise RuntimeError("boom")
|
|
125
|
+
|
|
126
|
+
plugin._load_policy_q = boom
|
|
127
|
+
plugin._policy_pending = None
|
|
128
|
+
old = NS["_bc_blocks"]
|
|
129
|
+
NS["_bc_blocks"] = types.SimpleNamespace(
|
|
130
|
+
BLOCKS=[types.SimpleNamespace(id="bio_emotion")],
|
|
131
|
+
_enabled_map=boom)
|
|
132
|
+
try:
|
|
133
|
+
out = await PAGE_DEBUG(plugin)
|
|
134
|
+
finally:
|
|
135
|
+
NS["_bc_blocks"] = old
|
|
136
|
+
self.assertEqual(out["status"], "ok")
|
|
137
|
+
self.assertEqual(out["data"]["policy"], {"q_size": None, "last_ctx": None})
|
|
138
|
+
self.assertEqual(out["data"]["blocks"], {"enabled": [], "skipped": []})
|
|
139
|
+
json.dumps(out)
|
|
140
|
+
|
|
141
|
+
async def test_route_registered(self):
|
|
142
|
+
self.assertIn("/astrbot_plugin_biocore/page/debug", SOURCE)
|
|
143
|
+
self.assertIn("self.page_debug", SOURCE)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
unittest.main()
|