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,146 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""P1-2 interoception -> expression style tests (intero_style.py + wiring)."""
|
|
3
|
+
import ast
|
|
4
|
+
import types
|
|
5
|
+
import unittest
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from biocore_model import intero_style as ist
|
|
9
|
+
|
|
10
|
+
ROOT = Path(__file__).resolve().parents[2]
|
|
11
|
+
MAIN = ROOT / "main.py"
|
|
12
|
+
SOURCE = MAIN.read_text(encoding="utf-8")
|
|
13
|
+
|
|
14
|
+
# three clearly different felt states
|
|
15
|
+
TENSE = {"tension": 0.9, "warmth": 0.15, "heaviness": 0.85, "clarity": 0.3}
|
|
16
|
+
WARM = {"tension": 0.1, "warmth": 0.9, "heaviness": 0.1, "clarity": 0.8}
|
|
17
|
+
FUZZY = {"tension": 0.45, "warmth": 0.5, "heaviness": 0.3, "clarity": 0.12}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LevelsTest(unittest.TestCase):
|
|
21
|
+
def test_levels_clamped(self):
|
|
22
|
+
self.assertEqual(ist.levels({"tension": 5, "warmth": -1}),
|
|
23
|
+
{"tension": 1.0, "warmth": 0.0,
|
|
24
|
+
"heaviness": 0.0, "clarity": 0.0})
|
|
25
|
+
|
|
26
|
+
def test_invalid_intero_is_neutral(self):
|
|
27
|
+
out = ist.style(None)
|
|
28
|
+
self.assertEqual(out["tone"], "")
|
|
29
|
+
self.assertEqual(out["target"], 10)
|
|
30
|
+
self.assertEqual(out["cap"], 30)
|
|
31
|
+
self.assertEqual(ist.initiative(None), 0.5)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class StyleDifferenceTest(unittest.TestCase):
|
|
35
|
+
def test_three_profiles_are_distinguishable(self):
|
|
36
|
+
tense = ist.style(TENSE)
|
|
37
|
+
warm = ist.style(WARM)
|
|
38
|
+
fuzzy = ist.style(FUZZY)
|
|
39
|
+
# length: tense/fuzzy shorter, warm longer
|
|
40
|
+
self.assertLess(tense["cap"], warm["cap"])
|
|
41
|
+
self.assertLess(fuzzy["cap"], warm["cap"])
|
|
42
|
+
self.assertLess(tense["target"], warm["target"])
|
|
43
|
+
# tone: each profile gets its own directive
|
|
44
|
+
self.assertIn("情绪", tense["tone"])
|
|
45
|
+
self.assertIn("亲近", warm["tone"])
|
|
46
|
+
self.assertIn("确认", fuzzy["tone"])
|
|
47
|
+
self.assertEqual(len({tense["tone"], warm["tone"], fuzzy["tone"]}), 3)
|
|
48
|
+
# length factor monotone: tense < fuzzy < warm
|
|
49
|
+
self.assertLess(tense["factor"], fuzzy["factor"])
|
|
50
|
+
self.assertLess(fuzzy["factor"], warm["factor"])
|
|
51
|
+
|
|
52
|
+
def test_initiative_ordering(self):
|
|
53
|
+
tense = ist.initiative(TENSE)
|
|
54
|
+
fuzzy = ist.initiative(FUZZY)
|
|
55
|
+
warm = ist.initiative(WARM)
|
|
56
|
+
self.assertLess(tense, fuzzy)
|
|
57
|
+
self.assertLess(fuzzy, warm)
|
|
58
|
+
self.assertGreaterEqual(tense, 0.0)
|
|
59
|
+
self.assertLessEqual(warm, 1.0)
|
|
60
|
+
|
|
61
|
+
def test_tension_and_heaviness_shorten(self):
|
|
62
|
+
self.assertLess(ist.length_factor(TENSE), 1.0)
|
|
63
|
+
only_tense = ist.style({"tension": 1.0}, target=10, cap=30)
|
|
64
|
+
self.assertLess(only_tense["cap"], 30)
|
|
65
|
+
only_heavy = ist.style({"heaviness": 1.0}, target=10, cap=30)
|
|
66
|
+
self.assertLess(only_heavy["cap"], 30)
|
|
67
|
+
|
|
68
|
+
def test_warmth_lengthens(self):
|
|
69
|
+
warm = ist.style(WARM, target=10, cap=30)
|
|
70
|
+
self.assertGreater(warm["cap"], 30)
|
|
71
|
+
self.assertGreater(warm["target"], 10)
|
|
72
|
+
|
|
73
|
+
def test_low_clarity_asks_to_confirm(self):
|
|
74
|
+
self.assertIn("确认", ist.tone({"clarity": 0.1}))
|
|
75
|
+
self.assertEqual(ist.tone({"clarity": 0.9}), "")
|
|
76
|
+
|
|
77
|
+
def test_bounds_and_floor(self):
|
|
78
|
+
out = ist.style(TENSE, target=1, cap=1)
|
|
79
|
+
self.assertGreaterEqual(out["target"], ist.MIN_TARGET)
|
|
80
|
+
self.assertGreaterEqual(out["cap"], out["target"])
|
|
81
|
+
self.assertLessEqual(out["cap"], ist.MAX_CAP)
|
|
82
|
+
tiny = ist.style({"tension": 1.0, "heaviness": 1.0}, target=10,
|
|
83
|
+
cap=30)
|
|
84
|
+
self.assertGreaterEqual(tiny["cap"], ist.MIN_CAP)
|
|
85
|
+
|
|
86
|
+
def test_deterministic(self):
|
|
87
|
+
self.assertEqual(ist.style(WARM), ist.style(dict(WARM)))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _load_intero_helper():
|
|
91
|
+
tree = ast.parse(SOURCE)
|
|
92
|
+
node = next(item for item in tree.body
|
|
93
|
+
if isinstance(item, ast.FunctionDef)
|
|
94
|
+
and item.name == "_intero_style")
|
|
95
|
+
clone = ast.FunctionDef(name=node.name, args=node.args, body=node.body,
|
|
96
|
+
decorator_list=[], returns=node.returns,
|
|
97
|
+
type_comment=None, lineno=node.lineno,
|
|
98
|
+
col_offset=node.col_offset,
|
|
99
|
+
end_lineno=node.end_lineno,
|
|
100
|
+
end_col_offset=node.end_col_offset)
|
|
101
|
+
ns = {"logger": types.SimpleNamespace(
|
|
102
|
+
info=lambda *a, **k: None, warning=lambda *a, **k: None,
|
|
103
|
+
debug=lambda *a, **k: None)}
|
|
104
|
+
exec(compile(ast.Module(body=[clone], type_ignores=[]),
|
|
105
|
+
"<_intero_style>", "exec"), ns)
|
|
106
|
+
return ns[node.name]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
HELPER = _load_intero_helper()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class FakeCore:
|
|
113
|
+
def __init__(self, intero):
|
|
114
|
+
self.intero = intero
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class WiringTest(unittest.TestCase):
|
|
118
|
+
def test_main_wiring_markers(self):
|
|
119
|
+
self.assertIn("def _intero_style(core, target, cap)", SOURCE)
|
|
120
|
+
self.assertIn("_intero_style(", SOURCE)
|
|
121
|
+
self.assertIn("intero_style as _intero", SOURCE)
|
|
122
|
+
self.assertIn("expression_style", SOURCE)
|
|
123
|
+
self.assertGreaterEqual(SOURCE.count("_istyle = _intero_style("), 2)
|
|
124
|
+
|
|
125
|
+
def test_helper_applies_and_exposes_style(self):
|
|
126
|
+
core = FakeCore(TENSE)
|
|
127
|
+
out = HELPER(core, 10, 30)
|
|
128
|
+
self.assertEqual(out["tone"], ist.style(TENSE)["tone"])
|
|
129
|
+
self.assertLess(out["cap"], 30)
|
|
130
|
+
self.assertEqual(core.expression_style["tone"], out["tone"])
|
|
131
|
+
|
|
132
|
+
def test_helper_failure_is_identity(self):
|
|
133
|
+
class BoomCore:
|
|
134
|
+
@property
|
|
135
|
+
def intero(self):
|
|
136
|
+
raise RuntimeError("boom")
|
|
137
|
+
|
|
138
|
+
core = BoomCore()
|
|
139
|
+
out = HELPER(core, 12, 32)
|
|
140
|
+
self.assertEqual(out["target"], 12)
|
|
141
|
+
self.assertEqual(out["cap"], 32)
|
|
142
|
+
self.assertEqual(out["tone"], "")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
if __name__ == "__main__":
|
|
146
|
+
unittest.main()
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""item-3: LEX + bio_state text emotion word lists are gone; numeric path only."""
|
|
3
|
+
import inspect
|
|
4
|
+
import unittest
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from biocore_model import bio_const, bio_emotion, bio_state
|
|
8
|
+
from biocore_model.bio_state import StateMixin
|
|
9
|
+
|
|
10
|
+
PKG = Path(__file__).resolve().parents[1]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Dummy:
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self.conflict_ema = 0.0
|
|
16
|
+
self.rumination_ema = 0.0
|
|
17
|
+
self.rumination_suppress = False
|
|
18
|
+
self.rumination_effect_enabled = False
|
|
19
|
+
self._neg_repeat = 0
|
|
20
|
+
self._last_neg_topic = ""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TableRemovalTest(unittest.TestCase):
|
|
24
|
+
def test_bio_const_has_no_lex(self):
|
|
25
|
+
self.assertFalse(hasattr(bio_const, "LEX"))
|
|
26
|
+
|
|
27
|
+
def test_bio_emotion_source_has_no_lex(self):
|
|
28
|
+
source = (PKG / "bio_emotion.py").read_text(encoding="utf-8")
|
|
29
|
+
self.assertNotIn("LEX", source)
|
|
30
|
+
|
|
31
|
+
def test_bio_emotion_has_no_command_word_lists(self):
|
|
32
|
+
source = (PKG / "bio_emotion.py").read_text(encoding="utf-8")
|
|
33
|
+
for word in ("必须", "服从", "不许", "麻烦"):
|
|
34
|
+
self.assertNotIn(word, source, word)
|
|
35
|
+
|
|
36
|
+
def test_bio_state_has_no_text_emotion_words(self):
|
|
37
|
+
source = (PKG / "bio_state.py").read_text(encoding="utf-8")
|
|
38
|
+
for word in ("不对", "你错了", "矛盾", "难受", "委屈", "崩溃"):
|
|
39
|
+
self.assertNotIn(word, source, word)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class NumericConflictTest(unittest.TestCase):
|
|
43
|
+
def test_goal_blocked_raises_conflict(self):
|
|
44
|
+
dummy = Dummy()
|
|
45
|
+
out = StateMixin.update_conflict_ema(dummy, "随便什么字",
|
|
46
|
+
dims={"goal": -1.0})
|
|
47
|
+
self.assertGreater(out["delta"], 0.0)
|
|
48
|
+
self.assertGreater(out["conflict_ema"], 0.0)
|
|
49
|
+
|
|
50
|
+
def test_blame_raises_conflict(self):
|
|
51
|
+
dummy = Dummy()
|
|
52
|
+
out = StateMixin.update_conflict_ema(dummy, "", dims={"blame": 1.0})
|
|
53
|
+
self.assertGreater(out["delta"], 0.3)
|
|
54
|
+
|
|
55
|
+
def test_text_words_are_ignored(self):
|
|
56
|
+
dummy = Dummy()
|
|
57
|
+
out = StateMixin.update_conflict_ema(
|
|
58
|
+
dummy, "不对 你错了 矛盾 但是 说错了 纠正")
|
|
59
|
+
self.assertEqual(out["delta"], 0.0)
|
|
60
|
+
|
|
61
|
+
def test_correction_flag_still_counted(self):
|
|
62
|
+
dummy = Dummy()
|
|
63
|
+
plain = StateMixin.update_conflict_ema(dummy, "", dims={})
|
|
64
|
+
dummy2 = Dummy()
|
|
65
|
+
flagged = StateMixin.update_conflict_ema(dummy2, "", correction=True)
|
|
66
|
+
self.assertGreater(flagged["delta"], plain["delta"])
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class NumericRuminationTest(unittest.TestCase):
|
|
70
|
+
def test_negative_label_increases(self):
|
|
71
|
+
dummy = Dummy()
|
|
72
|
+
out = StateMixin.update_rumination_ema(dummy, "", emotion="sadness",
|
|
73
|
+
intensity=0.8)
|
|
74
|
+
self.assertGreater(out["rumination_ema"], 0.0)
|
|
75
|
+
|
|
76
|
+
def test_text_words_without_signal_do_not_increase(self):
|
|
77
|
+
dummy = Dummy()
|
|
78
|
+
out = StateMixin.update_rumination_ema(dummy, "难受 难过 委屈 崩溃",
|
|
79
|
+
emotion="neutral")
|
|
80
|
+
self.assertEqual(out["rumination_ema"], 0.0)
|
|
81
|
+
self.assertFalse(out["repeat"])
|
|
82
|
+
|
|
83
|
+
def test_dims_hit_marks_negative(self):
|
|
84
|
+
dummy = Dummy()
|
|
85
|
+
out = StateMixin.update_rumination_ema(dummy, "", emotion="neutral",
|
|
86
|
+
dims={"hit": 0.8})
|
|
87
|
+
self.assertGreater(out["rumination_ema"], 0.0)
|
|
88
|
+
|
|
89
|
+
def test_repeat_same_label_counts(self):
|
|
90
|
+
dummy = Dummy()
|
|
91
|
+
StateMixin.update_rumination_ema(dummy, "", emotion="anger")
|
|
92
|
+
second = StateMixin.update_rumination_ema(dummy, "", emotion="anger")
|
|
93
|
+
self.assertTrue(second["repeat"])
|
|
94
|
+
self.assertEqual(second["repeat_count"], 2)
|
|
95
|
+
|
|
96
|
+
def test_neutral_decays(self):
|
|
97
|
+
dummy = Dummy()
|
|
98
|
+
dummy.rumination_ema = 0.8
|
|
99
|
+
out = StateMixin.update_rumination_ema(dummy, "", emotion="neutral",
|
|
100
|
+
novelty=0.9)
|
|
101
|
+
self.assertLess(out["rumination_ema"], 0.8)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
unittest.main()
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""item-6: goal/identity scoring is numeric (probe dims), no word tables."""
|
|
3
|
+
import unittest
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from biocore_model.bio_goal import GoalMixin
|
|
7
|
+
from biocore_model.bio_identity import IdentityMixin
|
|
8
|
+
|
|
9
|
+
PKG = Path(__file__).resolve().parents[1]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GoalDummy(GoalMixin):
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.goals = [
|
|
15
|
+
{"key": "a", "name": "Goal A", "priority": 0.9, "progress": 0.0},
|
|
16
|
+
{"key": "b", "name": "Goal B", "priority": 0.3, "progress": 0.0},
|
|
17
|
+
]
|
|
18
|
+
self.appraisal = {}
|
|
19
|
+
self.goal_scores = {}
|
|
20
|
+
self.goal_focus = ""
|
|
21
|
+
self.goal_conflict = []
|
|
22
|
+
self.a = 0.5
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def _text_similarity(left, right):
|
|
26
|
+
return 0.0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class IdentityDummy(IdentityMixin):
|
|
30
|
+
def __init__(self):
|
|
31
|
+
self.trust = 0.5
|
|
32
|
+
self.v = 0.0
|
|
33
|
+
self.stress = 0.1
|
|
34
|
+
self.echo_trace = 0.0
|
|
35
|
+
self.identity_threat_stress_enabled = False
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SourceAuditTest(unittest.TestCase):
|
|
39
|
+
def test_bio_goal_has_no_positive_negative_word_table(self):
|
|
40
|
+
source = (PKG / "bio_goal.py").read_text(encoding="utf-8")
|
|
41
|
+
self.assertNotIn("_pos = sum", source)
|
|
42
|
+
self.assertNotIn("_neg = sum", source)
|
|
43
|
+
for word in ("谢谢", "废物", "闭嘴", "讨厌"):
|
|
44
|
+
self.assertNotIn(word, source, word)
|
|
45
|
+
|
|
46
|
+
def test_bio_identity_has_no_word_tables(self):
|
|
47
|
+
source = (PKG / "bio_identity.py").read_text(encoding="utf-8")
|
|
48
|
+
for word in ("你不是她", "不配", "cold_words", "warm_words",
|
|
49
|
+
"废物", "傻子"):
|
|
50
|
+
self.assertNotIn(word, source, word)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class IdentityNumericTest(unittest.TestCase):
|
|
54
|
+
def test_text_alone_is_not_a_threat(self):
|
|
55
|
+
dummy = IdentityDummy()
|
|
56
|
+
self.assertFalse(dummy.detect_identity_threat(
|
|
57
|
+
"你不是她,你就是个工具废物"))
|
|
58
|
+
|
|
59
|
+
def test_dims_blame_and_involve_are_a_threat(self):
|
|
60
|
+
dummy = IdentityDummy()
|
|
61
|
+
self.assertTrue(dummy.detect_identity_threat(
|
|
62
|
+
dims={"blame": 0.8, "involve": 0.6}))
|
|
63
|
+
|
|
64
|
+
def test_dims_hit_is_a_threat(self):
|
|
65
|
+
self.assertTrue(IdentityDummy().detect_identity_threat(
|
|
66
|
+
dims={"hit": 0.8}))
|
|
67
|
+
|
|
68
|
+
def test_apply_is_default_off(self):
|
|
69
|
+
dummy = IdentityDummy()
|
|
70
|
+
self.assertFalse(dummy.apply_identity_threat(dims={"hit": 0.9}))
|
|
71
|
+
self.assertAlmostEqual(dummy.stress, 0.1)
|
|
72
|
+
|
|
73
|
+
def test_apply_enabled_bumps_stress(self):
|
|
74
|
+
dummy = IdentityDummy()
|
|
75
|
+
dummy.identity_threat_stress_enabled = True
|
|
76
|
+
self.assertTrue(dummy.apply_identity_threat(dims={"hit": 0.9}))
|
|
77
|
+
self.assertGreater(dummy.stress, 0.1)
|
|
78
|
+
|
|
79
|
+
def test_feedback_user_numeric_cold_and_warm(self):
|
|
80
|
+
cold = IdentityDummy()
|
|
81
|
+
cold.feedback_user(cold=1.0)
|
|
82
|
+
self.assertLess(cold.trust, 0.5)
|
|
83
|
+
warm = IdentityDummy()
|
|
84
|
+
warm.feedback_user(warm=1.0)
|
|
85
|
+
self.assertGreater(warm.trust, 0.5)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class GoalNumericTest(unittest.TestCase):
|
|
89
|
+
def test_goal_relevance_from_dims(self):
|
|
90
|
+
dummy = GoalDummy()
|
|
91
|
+
self.assertGreater(dummy._goal_relevance_from_dims(
|
|
92
|
+
{"involve": 1.0}), 0.4)
|
|
93
|
+
self.assertEqual(dummy._goal_relevance_from_dims({}), 0.0)
|
|
94
|
+
|
|
95
|
+
def test_update_goals_uses_dims(self):
|
|
96
|
+
dummy = GoalDummy()
|
|
97
|
+
dummy._update_goals("随便什么字", dims={"involve": 1.0, "blame": 0.5})
|
|
98
|
+
self.assertGreater(dummy.appraisal["goal_relevance"], 0.0)
|
|
99
|
+
self.assertTrue(all(v > 0.0 for v in dummy.goal_scores.values()))
|
|
100
|
+
self.assertLess(dummy.appraisal["norm_compatibility"], 0.5)
|
|
101
|
+
|
|
102
|
+
def test_update_goals_ignores_text_words_without_dims(self):
|
|
103
|
+
dummy = GoalDummy()
|
|
104
|
+
dummy._update_goals("谢谢 喜欢 谢谢")
|
|
105
|
+
self.assertEqual(dummy.appraisal["goal_relevance"], 0.0)
|
|
106
|
+
|
|
107
|
+
def test_goal_evidence_dims_beats_text(self):
|
|
108
|
+
dummy = GoalDummy()
|
|
109
|
+
goal = dummy.goals[0]
|
|
110
|
+
self.assertGreater(dummy._goal_evidence(goal, "", "",
|
|
111
|
+
dims={"hit": 1.0}), 0.0)
|
|
112
|
+
self.assertEqual(dummy._goal_evidence(goal, "谢谢 喜欢", ""), 0.0)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == "__main__":
|
|
116
|
+
unittest.main()
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
from biocore_model import layered_memory as lm
|
|
5
|
+
|
|
6
|
+
NOW = 1_700_000_000
|
|
7
|
+
DAY = 86400
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _entry(eid, text, ts=0, importance=0.5, role="user", **extra):
|
|
11
|
+
data = {"id": eid, "text": text, "role": role, "ts": ts,
|
|
12
|
+
"importance": importance}
|
|
13
|
+
data.update(extra)
|
|
14
|
+
return data
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _resident(eid, text, ts=0, importance=0.5, **extra):
|
|
18
|
+
return _entry(eid, text, ts, importance, tags=["resident"], **extra)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _section(text, header):
|
|
22
|
+
out, inside = [], False
|
|
23
|
+
for line in text.splitlines():
|
|
24
|
+
if line.strip() in ("常驻事实:", "相关事件:"):
|
|
25
|
+
inside = line.strip() == header
|
|
26
|
+
continue
|
|
27
|
+
if inside:
|
|
28
|
+
out.append(line)
|
|
29
|
+
return out
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class LayeredMemoryTest(unittest.TestCase):
|
|
33
|
+
def test_classification_uses_only_tags_kind_and_pin(self):
|
|
34
|
+
# no keyword heuristics: a factual sentence without a tag stays retrieval
|
|
35
|
+
plain = _entry("x1", "我姓王,我妈住院了")
|
|
36
|
+
self.assertEqual(lm.classify(plain), "retrieval")
|
|
37
|
+
tagged = _entry("x2", "我姓王", tags=["resident"])
|
|
38
|
+
self.assertEqual(lm.classify(tagged), "resident")
|
|
39
|
+
kinded = _entry("x3", "我姓王", kind="profile")
|
|
40
|
+
self.assertEqual(lm.classify(kinded), "resident")
|
|
41
|
+
pinned = _entry("x4", "我姓王", pin=True)
|
|
42
|
+
self.assertEqual(lm.classify(pinned), "resident")
|
|
43
|
+
self.assertEqual(lm.classify(_entry("x5", "我姓王", deleted=True)), "")
|
|
44
|
+
|
|
45
|
+
def test_resident_facts_plus_capped_retrieval(self):
|
|
46
|
+
entries = [
|
|
47
|
+
_resident("f1", "小夏,昵称夏夏", NOW - 5 * DAY, 0.8),
|
|
48
|
+
_resident("f2", "不喜欢下雨", NOW - 4 * DAY, 0.7),
|
|
49
|
+
_entry("e1", "周末打算去海边", NOW - 3 * DAY),
|
|
50
|
+
_entry("e2", "周五陪小夏体检", NOW - 2 * DAY),
|
|
51
|
+
_entry("e3", "项目报告下周五截止", NOW - DAY),
|
|
52
|
+
]
|
|
53
|
+
out = lm.render(entries, recalled_ids=["e1", "e2", "e3"], now=NOW)
|
|
54
|
+
self.assertIn("[分层记忆]", out)
|
|
55
|
+
self.assertIn("夏夏", out)
|
|
56
|
+
self.assertIn("海边", out)
|
|
57
|
+
self.assertEqual(len(_section(out, "相关事件:")), 2)
|
|
58
|
+
|
|
59
|
+
def test_retrieval_limit_is_configurable_and_clamped(self):
|
|
60
|
+
entries = [_resident("f1", "昵称小七", NOW, 0.9)]
|
|
61
|
+
entries += [_entry("e%d" % i, "事件 %d" % i, NOW - i * DAY)
|
|
62
|
+
for i in range(1, 7)]
|
|
63
|
+
one = lm.render(entries, retrieval_limit=1, now=NOW)
|
|
64
|
+
self.assertEqual(len(_section(one, "相关事件:")), 1)
|
|
65
|
+
zero = lm.render(entries, retrieval_limit=0, now=NOW)
|
|
66
|
+
self.assertNotIn("相关事件:", zero)
|
|
67
|
+
self.assertIn("小七", zero)
|
|
68
|
+
huge = lm.render(entries, retrieval_limit=99, now=NOW)
|
|
69
|
+
self.assertEqual(len(_section(huge, "相关事件:")),
|
|
70
|
+
lm.HARD_RETRIEVAL_LIMIT)
|
|
71
|
+
|
|
72
|
+
def test_resident_facts_survive_three_days(self):
|
|
73
|
+
entries = [
|
|
74
|
+
_resident("f1", "叫他夏夏", NOW - 10 * DAY, 0.9),
|
|
75
|
+
_resident("f2", "最怕打雷", NOW - 9 * DAY, 0.6),
|
|
76
|
+
_entry("e1", "上周去了海边", NOW - 3 * DAY),
|
|
77
|
+
]
|
|
78
|
+
for day in range(3):
|
|
79
|
+
out = lm.render(entries, recalled_ids=["e1"],
|
|
80
|
+
now=NOW + day * DAY)
|
|
81
|
+
self.assertIn("夏夏", out)
|
|
82
|
+
self.assertIn("打雷", out)
|
|
83
|
+
self.assertIn("常驻事实:", out)
|
|
84
|
+
|
|
85
|
+
def test_recalled_order_is_respected(self):
|
|
86
|
+
entries = [_entry("e1", "第一个事件", NOW - DAY),
|
|
87
|
+
_entry("e2", "第二个事件", NOW - 2 * DAY),
|
|
88
|
+
_entry("e3", "第三个事件", NOW - 3 * DAY)]
|
|
89
|
+
out = lm.render(entries, recalled_ids=["e3", "e1", "e2"], now=NOW)
|
|
90
|
+
rows = _section(out, "相关事件:")
|
|
91
|
+
self.assertIn("第三个事件", rows[0])
|
|
92
|
+
self.assertIn("第一个事件", rows[1])
|
|
93
|
+
|
|
94
|
+
def test_resident_entries_never_repeat_in_retrieval(self):
|
|
95
|
+
entries = [_resident("f1", "叫他阿夏", NOW, 0.9),
|
|
96
|
+
_entry("e1", "昨天一起吃了火锅", NOW - DAY)]
|
|
97
|
+
out = lm.render(entries, recalled_ids=["f1", "e1"], now=NOW)
|
|
98
|
+
rows = _section(out, "相关事件:")
|
|
99
|
+
self.assertEqual(len(rows), 1)
|
|
100
|
+
self.assertNotIn("阿夏", rows[0])
|
|
101
|
+
|
|
102
|
+
def test_duplicate_facts_collapse(self):
|
|
103
|
+
entries = [_resident("f1", "昵称是夏夏", NOW, 0.8),
|
|
104
|
+
_resident("f2", "昵称是夏夏!", NOW - DAY, 0.7)]
|
|
105
|
+
out = lm.render(entries, now=NOW)
|
|
106
|
+
self.assertEqual(out.count("夏夏"), 1)
|
|
107
|
+
|
|
108
|
+
def test_deleted_and_summarized_entries_are_skipped(self):
|
|
109
|
+
entries = [_resident("f1", "叫小夏", NOW, 0.9, deleted=True),
|
|
110
|
+
_resident("f2", "喜欢下雨", NOW, 0.9, summarized=True),
|
|
111
|
+
_resident("f3", "长期摘要", NOW, 0.9, role="summary")]
|
|
112
|
+
self.assertEqual(lm.render(entries, now=NOW), "")
|
|
113
|
+
|
|
114
|
+
def test_pinned_event_becomes_resident(self):
|
|
115
|
+
entries = [_entry("e1", "明天下午三点开会", NOW - DAY, 0.4, pin=True)]
|
|
116
|
+
out = lm.render(entries, now=NOW)
|
|
117
|
+
self.assertIn("常驻事实:", out)
|
|
118
|
+
self.assertIn("三点开会", out)
|
|
119
|
+
self.assertNotIn("相关事件:", out)
|
|
120
|
+
|
|
121
|
+
def test_empty_and_malformed_inputs(self):
|
|
122
|
+
self.assertEqual(lm.render(None), "")
|
|
123
|
+
self.assertEqual(lm.render([]), "")
|
|
124
|
+
self.assertEqual(lm.render([None, "x", {}, {"text": " "}]), "")
|
|
125
|
+
|
|
126
|
+
def test_render_is_bounded_and_trims(self):
|
|
127
|
+
long_fact = "很长的偏好描述" * 60
|
|
128
|
+
entries = [_resident("f1", long_fact, NOW, 0.9),
|
|
129
|
+
_entry("e1", "事件" * 80, NOW - DAY)]
|
|
130
|
+
out = lm.render(entries, now=NOW)
|
|
131
|
+
self.assertLessEqual(len(out), lm.MAX_CHARS)
|
|
132
|
+
self.assertIn("…", out)
|
|
133
|
+
rows = _section(out, "相关事件:")
|
|
134
|
+
self.assertEqual(len(rows), 1)
|
|
135
|
+
self.assertLessEqual(len(rows[0]), lm.EVENT_TRIM + 8)
|
|
136
|
+
|
|
137
|
+
def test_fact_entry_validation(self):
|
|
138
|
+
self.assertIsNone(lm.fact_entry("", 0.9))
|
|
139
|
+
self.assertIsNone(lm.fact_entry("他姓王", 0.3))
|
|
140
|
+
candidate = lm.fact_entry("他姓王", 0.8)
|
|
141
|
+
self.assertEqual(candidate["text"], "他姓王")
|
|
142
|
+
self.assertEqual(candidate["tags"], ["resident"])
|
|
143
|
+
self.assertEqual(candidate["memory_source"], "probe_fact")
|
|
144
|
+
long_fact = "他" * 40
|
|
145
|
+
self.assertEqual(len(lm.fact_entry(long_fact, 0.9)["text"]),
|
|
146
|
+
lm.FACT_MAX_CHARS)
|
|
147
|
+
|
|
148
|
+
def test_duplicate_resident_detection(self):
|
|
149
|
+
entries = [_resident("f1", "他姓王,住在杭州")]
|
|
150
|
+
self.assertTrue(lm.duplicate_resident("他姓王,住在杭州", entries))
|
|
151
|
+
self.assertTrue(lm.duplicate_resident("他姓王住在杭州啊", entries))
|
|
152
|
+
self.assertFalse(lm.duplicate_resident("他喜欢打游戏", entries))
|
|
153
|
+
# a non-resident entry with the same text is not a resident duplicate
|
|
154
|
+
self.assertFalse(lm.duplicate_resident(
|
|
155
|
+
"他姓王,住在杭州", [_entry("e1", "他姓王,住在杭州")]))
|
|
156
|
+
|
|
157
|
+
def test_should_write_fact_once_per_message(self):
|
|
158
|
+
entries = [_resident("f1", "他姓王")]
|
|
159
|
+
written = {}
|
|
160
|
+
self.assertTrue(lm.should_write_fact("他喜欢打游戏", 0.8, entries,
|
|
161
|
+
written, "msg-1"))
|
|
162
|
+
written["msg-1"] = 1.0
|
|
163
|
+
self.assertFalse(lm.should_write_fact("他喜欢打游戏", 0.8, entries,
|
|
164
|
+
written, "msg-1"))
|
|
165
|
+
self.assertFalse(lm.should_write_fact("他姓王", 0.8, entries, {},
|
|
166
|
+
"msg-2"))
|
|
167
|
+
self.assertFalse(lm.should_write_fact("他喜欢打游戏", 0.4, entries, {},
|
|
168
|
+
"msg-3"))
|
|
169
|
+
self.assertTrue(lm.should_write_fact("他喜欢打游戏", 0.8, entries, {},
|
|
170
|
+
"msg-3"))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
if __name__ == "__main__":
|
|
174
|
+
unittest.main()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import unittest
|
|
3
|
+
import interpretation
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ProtocolEchoStripTest(unittest.TestCase):
|
|
7
|
+
def setUp(self):
|
|
8
|
+
interpretation.LEAK_GUARD.update({'protocol_echo_stripped': 0, 'prompt_rule_echo': 0, 'protocol_echo_seen': 0})
|
|
9
|
+
|
|
10
|
+
def test_echo_sentence_at_end_is_removed(self):
|
|
11
|
+
visible, _ = interpretation.parse_response('今天天气不错。标签必须隐藏,不要解释。')
|
|
12
|
+
self.assertNotIn('必须隐藏', visible)
|
|
13
|
+
self.assertIn('今天天气不错', visible)
|
|
14
|
+
self.assertEqual(interpretation.LEAK_GUARD['protocol_echo_stripped'], 1)
|
|
15
|
+
|
|
16
|
+
def test_echo_sentence_in_middle_keeps_neighbours(self):
|
|
17
|
+
visible, _ = interpretation.parse_response('我明白你的意思。标签只出现一次,必须隐藏。我们继续聊。')
|
|
18
|
+
self.assertNotIn('标签只出现一次', visible)
|
|
19
|
+
self.assertIn('我明白', visible)
|
|
20
|
+
self.assertIn('我们继续聊', visible)
|
|
21
|
+
|
|
22
|
+
def test_multiple_echo_sentences_all_removed(self):
|
|
23
|
+
visible, _ = interpretation.parse_response('标签格式说明。不要解释。正常内容。')
|
|
24
|
+
self.assertNotIn('<bc>', visible)
|
|
25
|
+
self.assertNotIn('不要解释', visible)
|
|
26
|
+
self.assertIn('正常内容', visible)
|
|
27
|
+
|
|
28
|
+
def test_normal_hidden_word_not_stripped(self):
|
|
29
|
+
visible, _ = interpretation.parse_response('这件事我一直隐藏着没说。')
|
|
30
|
+
self.assertEqual(visible.strip(), '这件事我一直隐藏着没说。')
|
|
31
|
+
|
|
32
|
+
def test_rule_echo_kept_but_counted(self):
|
|
33
|
+
visible, _ = interpretation.parse_response('先接住情绪,再谈事情;今天想聊什么?')
|
|
34
|
+
self.assertIn('先接住情绪', visible)
|
|
35
|
+
self.assertEqual(interpretation.LEAK_GUARD['prompt_rule_echo'], 1)
|
|
36
|
+
|
|
37
|
+
def test_normal_reply_no_metrics(self):
|
|
38
|
+
visible, _ = interpretation.parse_response('晚安,做个好梦。')
|
|
39
|
+
self.assertEqual(visible.strip(), '晚安,做个好梦。')
|
|
40
|
+
self.assertEqual(interpretation.LEAK_GUARD['prompt_rule_echo'], 0)
|
|
41
|
+
|
|
42
|
+
def test_tag_plus_json_plus_fence_no_leak(self):
|
|
43
|
+
visible, _ = interpretation.parse_response('你好。\n```json\n{"e":"sad","i":0.5}\n```\n<bc>{"e":"sad","i":0.5}</bc>\n必须隐藏。')
|
|
44
|
+
self.assertNotIn('<bc>', visible)
|
|
45
|
+
self.assertNotIn('"e"', visible)
|
|
46
|
+
self.assertNotIn('```', visible)
|
|
47
|
+
self.assertIn('你好', visible)
|
|
48
|
+
|
|
49
|
+
def test_unclosed_tag_removed(self):
|
|
50
|
+
visible, _ = interpretation.parse_response('在的。<bc>{"e":"joy","i":0.4}')
|
|
51
|
+
self.assertNotIn('<bc>', visible)
|
|
52
|
+
self.assertIn('在的', visible)
|
|
53
|
+
|
|
54
|
+
def test_bad_json_label_not_crash(self):
|
|
55
|
+
visible, _ = interpretation.parse_response('嗯。<bc>not json</bc>')
|
|
56
|
+
self.assertNotIn('<bc>', visible)
|
|
57
|
+
|
|
58
|
+
def test_echo_only_reply_becomes_empty(self):
|
|
59
|
+
visible, _ = interpretation.parse_response('标签必须隐藏,不要解释。')
|
|
60
|
+
self.assertEqual(visible.strip(), '')
|
|
61
|
+
|
|
62
|
+
def test_leak_guard_snapshot_shape(self):
|
|
63
|
+
interpretation.parse_response('标签必须隐藏。开心吗?')
|
|
64
|
+
snap = interpretation.leak_guard_snapshot()
|
|
65
|
+
for key in ('protocol_echo', 'prompt_rule_echo', 'tag_missing', 'tag_invalid'):
|
|
66
|
+
self.assertIn(key, snap)
|
|
67
|
+
self.assertGreaterEqual(snap['protocol_echo'], 1)
|
|
68
|
+
|
|
69
|
+
def test_prompt_rule_multiple_occurrences_counted_once(self):
|
|
70
|
+
interpretation.parse_response('先接住情绪。先缓和。')
|
|
71
|
+
self.assertEqual(interpretation.LEAK_GUARD['prompt_rule_echo'], 1)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if __name__ == '__main__':
|
|
75
|
+
unittest.main()
|