dsh-biocore 0.1.2 → 0.1.4
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/lib/index.js +170 -27
- package/package.json +1 -1
- package/python/biocore_runtime/pipeline.py +42 -0
- package/python/biocore_runtime/rerank.py +201 -0
- package/python/mindbase/__init__.py +18 -0
- package/python/mindbase/__main__.py +3 -0
- package/python/mindbase/actions.py +56 -0
- package/python/mindbase/active_inference.py +54 -0
- package/python/mindbase/addressing.py +69 -0
- package/python/mindbase/affect.py +86 -0
- package/python/mindbase/affective.py +111 -0
- package/python/mindbase/allostasis.py +28 -0
- package/python/mindbase/appraisal.py +101 -0
- package/python/mindbase/attachment.py +49 -0
- package/python/mindbase/attention.py +30 -0
- package/python/mindbase/autonomy.py +81 -0
- package/python/mindbase/autonomy_bench.py +251 -0
- package/python/mindbase/autonomy_evolve.py +342 -0
- package/python/mindbase/autonomy_loop.py +65 -0
- package/python/mindbase/autonomy_meta.py +91 -0
- package/python/mindbase/base_only.py +28 -0
- package/python/mindbase/benchmark.py +61 -0
- package/python/mindbase/bio_adaptation.py +274 -0
- package/python/mindbase/bio_noise.py +39 -0
- package/python/mindbase/bio_semantic.py +138 -0
- package/python/mindbase/bm25.py +46 -0
- package/python/mindbase/bootstrap.py +256 -0
- package/python/mindbase/bootstrap_real.py +231 -0
- package/python/mindbase/brain.py +178 -0
- package/python/mindbase/care.py +78 -0
- package/python/mindbase/chaos_search.py +79 -0
- package/python/mindbase/character_profile.py +33 -0
- package/python/mindbase/circadian.py +38 -0
- package/python/mindbase/circuit.py +235 -0
- package/python/mindbase/clock.py +53 -0
- package/python/mindbase/companion_runtime.py +366 -0
- package/python/mindbase/companion_train.py +126 -0
- package/python/mindbase/config.py +66 -0
- package/python/mindbase/connection_state.py +76 -0
- package/python/mindbase/connectome_prior.py +35 -0
- package/python/mindbase/connectome_world.py +158 -0
- package/python/mindbase/constructs.py +88 -0
- package/python/mindbase/context_builder.py +58 -0
- package/python/mindbase/critical.py +36 -0
- package/python/mindbase/criticality.py +123 -0
- package/python/mindbase/defaults.py +27 -0
- package/python/mindbase/demo.py +163 -0
- package/python/mindbase/diary.py +116 -0
- package/python/mindbase/diary_service.py +40 -0
- package/python/mindbase/digital_prior.py +37 -0
- package/python/mindbase/diversity.py +110 -0
- package/python/mindbase/drives.py +106 -0
- package/python/mindbase/embedding.py +132 -0
- package/python/mindbase/embedding_memory.py +74 -0
- package/python/mindbase/embodiment.py +83 -0
- package/python/mindbase/emotion_calibration.py +39 -0
- package/python/mindbase/emotion_calibrator.py +50 -0
- package/python/mindbase/emotion_core.py +113 -0
- package/python/mindbase/emotion_dynamics.py +104 -0
- package/python/mindbase/emotion_intent.py +164 -0
- package/python/mindbase/emotion_layer.py +200 -0
- package/python/mindbase/emotion_schema.py +92 -0
- package/python/mindbase/engine.py +781 -0
- package/python/mindbase/episode_log.py +90 -0
- package/python/mindbase/evaluation.py +493 -0
- package/python/mindbase/event_appraisal.py +84 -0
- package/python/mindbase/evolution.py +77 -0
- package/python/mindbase/evolve_offline.py +232 -0
- package/python/mindbase/experiments.py +358 -0
- package/python/mindbase/expression.py +54 -0
- package/python/mindbase/external_brain.py +157 -0
- package/python/mindbase/fly_mb.py +159 -0
- package/python/mindbase/fly_tasks.py +45 -0
- package/python/mindbase/forage.py +179 -0
- package/python/mindbase/goal_generator.py +98 -0
- package/python/mindbase/goals.py +153 -0
- package/python/mindbase/grid_world.py +123 -0
- package/python/mindbase/group_autonomy.py +92 -0
- package/python/mindbase/group_context.py +67 -0
- package/python/mindbase/homeostatic.py +71 -0
- package/python/mindbase/homeostatic_action.py +38 -0
- package/python/mindbase/human_standard.py +53 -0
- package/python/mindbase/identity_suite.py +50 -0
- package/python/mindbase/image_context.py +27 -0
- package/python/mindbase/impression.py +119 -0
- package/python/mindbase/initiative_quota.py +145 -0
- package/python/mindbase/insight.py +51 -0
- package/python/mindbase/intelligence_search.py +108 -0
- package/python/mindbase/intention.py +76 -0
- package/python/mindbase/interaction_log.py +30 -0
- package/python/mindbase/interoception.py +67 -0
- package/python/mindbase/invariants.py +53 -0
- package/python/mindbase/iteration.py +29 -0
- package/python/mindbase/layered_memory.py +170 -0
- package/python/mindbase/layers/__init__.py +18 -0
- package/python/mindbase/layers/l0_substrate.py +39 -0
- package/python/mindbase/layers/l1_homeostasis.py +6 -0
- package/python/mindbase/layers/l2_affect.py +6 -0
- package/python/mindbase/layers/l3_attention.py +6 -0
- package/python/mindbase/layers/l4_learning.py +17 -0
- package/python/mindbase/layers/l5_self_social.py +35 -0
- package/python/mindbase/layers/l6_expression.py +23 -0
- package/python/mindbase/layers/l7_evaluation.py +45 -0
- package/python/mindbase/learner.py +196 -0
- package/python/mindbase/lexicon.py +33 -0
- package/python/mindbase/life_state.py +169 -0
- package/python/mindbase/lifecycle.py +87 -0
- package/python/mindbase/liquid_readout.py +48 -0
- package/python/mindbase/llm_adapter.py +233 -0
- package/python/mindbase/llm_cache.py +77 -0
- package/python/mindbase/llm_gate.py +85 -0
- package/python/mindbase/llm_openai.py +130 -0
- package/python/mindbase/llm_setup.py +99 -0
- package/python/mindbase/longrun.py +83 -0
- package/python/mindbase/longterm_memory.py +606 -0
- package/python/mindbase/mb/__init__.py +14 -0
- package/python/mindbase/mb/apl.py +22 -0
- package/python/mindbase/mb/dan.py +14 -0
- package/python/mindbase/mb/habituation.py +27 -0
- package/python/mindbase/mb/kc.py +28 -0
- package/python/mindbase/mb/mbon.py +23 -0
- package/python/mindbase/mb/mushroom_body.py +116 -0
- package/python/mindbase/memory.py +201 -0
- package/python/mindbase/memory_framework.py +158 -0
- package/python/mindbase/memory_retriever.py +110 -0
- package/python/mindbase/memory_summarizer.py +177 -0
- package/python/mindbase/memory_task.py +32 -0
- package/python/mindbase/memory_trace.py +46 -0
- package/python/mindbase/meta.py +32 -0
- package/python/mindbase/motivation.py +31 -0
- package/python/mindbase/multi_session.py +19 -0
- package/python/mindbase/narrative.py +87 -0
- package/python/mindbase/needs.py +77 -0
- package/python/mindbase/neural.py +45 -0
- package/python/mindbase/neuron_models.py +134 -0
- package/python/mindbase/noise.py +309 -0
- package/python/mindbase/one_on_one.py +60 -0
- package/python/mindbase/online_iteration.py +63 -0
- package/python/mindbase/ostracism.py +35 -0
- package/python/mindbase/parallel_eval.py +60 -0
- package/python/mindbase/parallel_qd.py +64 -0
- package/python/mindbase/persona.py +221 -0
- package/python/mindbase/persona_ab.py +64 -0
- package/python/mindbase/persona_bridge.py +129 -0
- package/python/mindbase/persona_compile.py +34 -0
- package/python/mindbase/persona_dsh.py +120 -0
- package/python/mindbase/persona_learner.py +157 -0
- package/python/mindbase/physiology.py +197 -0
- package/python/mindbase/population_lifecycle.py +83 -0
- package/python/mindbase/preferences.py +50 -0
- package/python/mindbase/proactive_agent.py +274 -0
- package/python/mindbase/proactive_gate.py +53 -0
- package/python/mindbase/proactive_group.py +119 -0
- package/python/mindbase/proactive_policy.py +422 -0
- package/python/mindbase/proactive_profile.py +196 -0
- package/python/mindbase/proactive_timing.py +28 -0
- package/python/mindbase/pyramid_stages.py +61 -0
- package/python/mindbase/quantum.py +132 -0
- package/python/mindbase/quantum_chaos.py +98 -0
- package/python/mindbase/reflection.py +68 -0
- package/python/mindbase/regulation_posture.py +50 -0
- package/python/mindbase/relationship_session.py +431 -0
- package/python/mindbase/relationship_suite.py +45 -0
- package/python/mindbase/relationships.py +70 -0
- package/python/mindbase/replay.py +22 -0
- package/python/mindbase/reply_diversity.py +53 -0
- package/python/mindbase/reply_gate.py +52 -0
- package/python/mindbase/rerank.py +41 -0
- package/python/mindbase/rerank_local.py +46 -0
- package/python/mindbase/ripple.py +187 -0
- package/python/mindbase/robust_search.py +68 -0
- package/python/mindbase/runtime.py +113 -0
- package/python/mindbase/scoped_identity.py +116 -0
- package/python/mindbase/self_insight.py +51 -0
- package/python/mindbase/self_model.py +116 -0
- package/python/mindbase/selftest.py +118 -0
- package/python/mindbase/semantic_memory.py +108 -0
- package/python/mindbase/session_store.py +175 -0
- package/python/mindbase/session_window.py +31 -0
- package/python/mindbase/shadow_eval.py +42 -0
- package/python/mindbase/sim_env.py +86 -0
- package/python/mindbase/social.py +138 -0
- package/python/mindbase/social_constraint.py +141 -0
- package/python/mindbase/social_feedback.py +95 -0
- package/python/mindbase/social_norm.py +225 -0
- package/python/mindbase/spiking_reservoir.py +204 -0
- package/python/mindbase/spontaneity.py +40 -0
- package/python/mindbase/story_loop.py +203 -0
- package/python/mindbase/survival_gauntlet.py +128 -0
- package/python/mindbase/symbolic.py +170 -0
- package/python/mindbase/teacher.py +244 -0
- package/python/mindbase/theory.py +42 -0
- package/python/mindbase/theory_of_mind.py +98 -0
- package/python/mindbase/translation_layer.py +80 -0
- package/python/mindbase/turn_eval.py +52 -0
- package/python/mindbase/types.py +78 -0
- package/python/mindbase/uncertainty_field.py +61 -0
- package/python/mindbase/user_loop.py +45 -0
- package/python/mindbase/user_profile.py +73 -0
- package/python/mindbase/value_model.py +64 -0
- package/python/mindbase/values.py +102 -0
- package/python/mindbase/vector_store.py +78 -0
- package/python/mindbase/viability.py +41 -0
- package/python/mindbase/virtual_body.py +90 -0
- package/python/mindbase/will.py +60 -0
- package/python/mindbase/working_memory.py +32 -0
- package/python/mindbase/workspace.py +55 -0
- package/ui/panel.css +16 -5
- package/ui/panel.html +25 -1
- package/ui/panel.js +57 -15
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Simulated chat environment for offline evolution.
|
|
2
|
+
|
|
3
|
+
Wraps MindBase + Kernel and a Teacher. No QQ, no network, no human in the loop.
|
|
4
|
+
Each episode is a sequence of user turns; the teacher provides the turn and the
|
|
5
|
+
outcome reward. The base's own decision modules remain in charge of actions.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from .types import Event
|
|
13
|
+
from .teacher import Scenario, Teacher
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class EpisodeResult:
|
|
18
|
+
seed: int
|
|
19
|
+
scenario: Scenario
|
|
20
|
+
turns: int = 0
|
|
21
|
+
reward_sum: float = 0.0
|
|
22
|
+
rewards: List[float] = field(default_factory=list)
|
|
23
|
+
actions: List[str] = field(default_factory=list)
|
|
24
|
+
states: List[Dict[str, float]] = field(default_factory=list)
|
|
25
|
+
noise_metrics: Dict[str, float] = field(default_factory=dict)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def mean_reward(self) -> float:
|
|
29
|
+
return self.reward_sum / self.turns if self.turns else 0.0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SimEnv:
|
|
33
|
+
"""Minimal simulated environment: user turns -> MindBase action -> reward."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, mind, kernel, teacher: Teacher, user: str = "user", dt: float = 60.0, noise_system=None):
|
|
36
|
+
self.mind = mind
|
|
37
|
+
self.kernel = kernel
|
|
38
|
+
self.teacher = teacher
|
|
39
|
+
self.user = str(user)
|
|
40
|
+
self.dt = float(dt)
|
|
41
|
+
self.noise_system = noise_system if noise_system is not None else getattr(mind, "endogenous_noise", None)
|
|
42
|
+
|
|
43
|
+
def run_episode(self, seed: int, max_turns: Optional[int] = None) -> EpisodeResult:
|
|
44
|
+
scenario = self.teacher.scenario(int(seed))
|
|
45
|
+
turns = list(scenario.turns)
|
|
46
|
+
if max_turns is not None:
|
|
47
|
+
turns = turns[:max(0, int(max_turns))]
|
|
48
|
+
result = EpisodeResult(seed=int(seed), scenario=scenario)
|
|
49
|
+
for turn in turns:
|
|
50
|
+
if self.noise_system is not None:
|
|
51
|
+
try:
|
|
52
|
+
self.noise_system.step(self.dt)
|
|
53
|
+
self.noise_system.inject(self.mind)
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
56
|
+
event = Event(text=str(turn.text), speaker=self.user,
|
|
57
|
+
directed=bool(turn.directed), ts=self.mind.t + self.dt,
|
|
58
|
+
domain="chat")
|
|
59
|
+
try:
|
|
60
|
+
self.kernel.observe(str(turn.text), self.user)
|
|
61
|
+
except Exception:
|
|
62
|
+
pass
|
|
63
|
+
action = self.kernel.step(event)
|
|
64
|
+
kind = str(getattr(action, "kind", "silence"))
|
|
65
|
+
reward = self.teacher.feedback(scenario, turn, kind, self.mind)
|
|
66
|
+
self.kernel.feedback(str(turn.text), "", float(reward), self.user)
|
|
67
|
+
result.turns += 1
|
|
68
|
+
result.reward_sum += float(reward)
|
|
69
|
+
result.rewards.append(float(reward))
|
|
70
|
+
result.actions.append(kind)
|
|
71
|
+
try:
|
|
72
|
+
result.states.append({
|
|
73
|
+
"v": float(getattr(self.mind.affect, "v", 0.0)),
|
|
74
|
+
"stress": float(getattr(self.mind.affect, "stress", 0.0)),
|
|
75
|
+
"energy": float(self.mind.drives.values.get("energy", 0.0)),
|
|
76
|
+
"social": float(self.mind.drives.values.get("social", 0.0)),
|
|
77
|
+
"viability": float(self.mind.viability_state.get("viability", 1.0)),
|
|
78
|
+
})
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
if self.noise_system is not None:
|
|
82
|
+
try:
|
|
83
|
+
result.noise_metrics = dict(self.noise_system.metrics())
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
86
|
+
return result
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import random
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Dict
|
|
7
|
+
|
|
8
|
+
from .config import MindConfig
|
|
9
|
+
from .noise import clip, ou
|
|
10
|
+
from .relationships import update_stage
|
|
11
|
+
from .types import Appraisal, Event
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class Bond:
|
|
16
|
+
attachment: float = 0.10
|
|
17
|
+
trust: float = 0.50
|
|
18
|
+
familiarity: float = 0.10
|
|
19
|
+
responsiveness: float = 0.50
|
|
20
|
+
valence: float = 0.0
|
|
21
|
+
last_seen: float = 0.0
|
|
22
|
+
stage: str = "stranger"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Social:
|
|
26
|
+
"""Per-speaker bonds + a slow group-valence state (contagion)."""
|
|
27
|
+
|
|
28
|
+
DAY = 86400.0
|
|
29
|
+
GRACE = 1.0 * DAY
|
|
30
|
+
TAU_FAMILIARITY = 30.0 * DAY
|
|
31
|
+
TAU_TRUST = 60.0 * DAY
|
|
32
|
+
TAU_ATTACHMENT = 90.0 * DAY
|
|
33
|
+
|
|
34
|
+
def __init__(self, cfg: MindConfig, rng: random.Random):
|
|
35
|
+
self.cfg = cfg
|
|
36
|
+
self.rng = rng
|
|
37
|
+
self.bonds: Dict[str, Bond] = {}
|
|
38
|
+
self.group_valence = 0.0
|
|
39
|
+
self.trust_gain = 1.0
|
|
40
|
+
|
|
41
|
+
def on_event(self, ev: Event, ap: Appraisal, now: float, dt: float) -> None:
|
|
42
|
+
if ev.kind == "message" and ev.speaker and ev.speaker != "self":
|
|
43
|
+
b = self.bonds.setdefault(ev.speaker, Bond())
|
|
44
|
+
b.familiarity = clip(b.familiarity + 0.010, 0.0, 1.0)
|
|
45
|
+
b.trust = clip(b.trust + 0.020 * ap.social * float(getattr(self, "trust_gain", 1.0)), 0.0, 1.0)
|
|
46
|
+
b.attachment = clip(b.attachment + 0.005 * max(0.0, ap.valence), 0.0, 1.0)
|
|
47
|
+
b.valence = ou(b.valence, ap.valence, 1.0 / 300.0, 0.05, dt, self.rng)
|
|
48
|
+
b.last_seen = now
|
|
49
|
+
update_stage(b)
|
|
50
|
+
self.group_valence = ou(
|
|
51
|
+
self.group_valence,
|
|
52
|
+
ap.valence,
|
|
53
|
+
1.0 / max(self.cfg.social_tau, 1e-6),
|
|
54
|
+
0.05,
|
|
55
|
+
dt,
|
|
56
|
+
self.rng,
|
|
57
|
+
)
|
|
58
|
+
self.group_valence = clip(self.group_valence, -1.0, 1.0)
|
|
59
|
+
|
|
60
|
+
def on_feedback(
|
|
61
|
+
self,
|
|
62
|
+
speaker: str,
|
|
63
|
+
feedback: float | None,
|
|
64
|
+
responded: bool = True,
|
|
65
|
+
now: float = 0.0,
|
|
66
|
+
dt: float = 60.0,
|
|
67
|
+
repair_factor: float = 1.0,
|
|
68
|
+
) -> None:
|
|
69
|
+
if not speaker or speaker == "self":
|
|
70
|
+
return
|
|
71
|
+
b = self.bonds.setdefault(speaker, Bond())
|
|
72
|
+
if responded:
|
|
73
|
+
fb = clip(feedback if feedback is not None else 0.0, -1.0, 1.0)
|
|
74
|
+
mult = 1.6 if fb < 0 else 1.0
|
|
75
|
+
gain = 0.04 * fb * mult * (float(repair_factor) if fb > 0 else 1.0) * float(getattr(self, "trust_gain", 1.0))
|
|
76
|
+
b.trust = clip(b.trust + gain, 0.0, 1.0)
|
|
77
|
+
b.responsiveness = clip(b.responsiveness + (0.02 * fb), 0.0, 1.0)
|
|
78
|
+
b.attachment = clip(b.attachment + 0.03 * max(0.0, fb), 0.0, 1.0)
|
|
79
|
+
b.familiarity = clip(b.familiarity + (0.005 if fb >= 0 else -0.012), 0.0, 1.0)
|
|
80
|
+
target = fb
|
|
81
|
+
else:
|
|
82
|
+
b.trust = clip(b.trust - 0.03, 0.0, 1.0)
|
|
83
|
+
b.attachment = clip(b.attachment - 0.02, 0.0, 1.0)
|
|
84
|
+
target = -0.40
|
|
85
|
+
b.valence = ou(b.valence, target, 1.0 / 300.0, 0.05, dt, self.rng)
|
|
86
|
+
b.last_seen = now
|
|
87
|
+
update_stage(b)
|
|
88
|
+
self.group_valence = clip(0.80 * self.group_valence + 0.20 * target, -1.0, 1.0)
|
|
89
|
+
|
|
90
|
+
def tick(self, dt: float, now: float) -> None:
|
|
91
|
+
"""Slow relationship decay when a bond has been idle beyond the grace period."""
|
|
92
|
+
if dt <= 0:
|
|
93
|
+
return
|
|
94
|
+
for b in self.bonds.values():
|
|
95
|
+
idle = max(0.0, now - b.last_seen)
|
|
96
|
+
if idle < self.GRACE:
|
|
97
|
+
continue
|
|
98
|
+
b.familiarity = clip(b.familiarity * math.exp(-dt / self.TAU_FAMILIARITY), 0.0, 1.0)
|
|
99
|
+
b.trust = clip(b.trust * math.exp(-dt / self.TAU_TRUST), 0.0, 1.0)
|
|
100
|
+
b.attachment = clip(b.attachment * math.exp(-dt / self.TAU_ATTACHMENT), 0.0, 1.0)
|
|
101
|
+
update_stage(b)
|
|
102
|
+
|
|
103
|
+
def repair(self, speaker: str, effort: float = 1.0, now: float = 0.0) -> Bond:
|
|
104
|
+
"""Active repair: apology / care raises trust and attachment."""
|
|
105
|
+
if not speaker or speaker == "self":
|
|
106
|
+
raise ValueError("repair needs a real speaker")
|
|
107
|
+
e = clip(float(effort), 0.0, 1.0)
|
|
108
|
+
b = self.bonds.setdefault(speaker, Bond())
|
|
109
|
+
b.trust = clip(b.trust + 0.06 * e, 0.0, 1.0)
|
|
110
|
+
b.attachment = clip(b.attachment + 0.05 * e, 0.0, 1.0)
|
|
111
|
+
b.familiarity = clip(b.familiarity + 0.01 * e, 0.0, 1.0)
|
|
112
|
+
b.valence = clip(b.valence + 0.20 * e, -1.0, 1.0)
|
|
113
|
+
b.last_seen = now
|
|
114
|
+
update_stage(b)
|
|
115
|
+
self.group_valence = clip(self.group_valence + 0.10 * e, -1.0, 1.0)
|
|
116
|
+
return b
|
|
117
|
+
|
|
118
|
+
def trust_for(self, speaker: str) -> float:
|
|
119
|
+
b = self.bonds.get(speaker)
|
|
120
|
+
return float(b.trust) if b else 0.0
|
|
121
|
+
|
|
122
|
+
def stage(self, speaker: str) -> str:
|
|
123
|
+
b = self.bonds.get(speaker)
|
|
124
|
+
return b.stage if b else "stranger"
|
|
125
|
+
|
|
126
|
+
def snapshot(self) -> Dict[str, object]:
|
|
127
|
+
return {
|
|
128
|
+
"group_valence": round(self.group_valence, 4),
|
|
129
|
+
"bonds": {
|
|
130
|
+
k: {
|
|
131
|
+
"attachment": round(v.attachment, 3),
|
|
132
|
+
"trust": round(v.trust, 3),
|
|
133
|
+
"familiarity": round(v.familiarity, 3),
|
|
134
|
+
"stage": v.stage,
|
|
135
|
+
}
|
|
136
|
+
for k, v in self.bonds.items()
|
|
137
|
+
},
|
|
138
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Numeric social constraint / autonomy boundary.
|
|
2
|
+
|
|
3
|
+
Constraints suppress behavior; they never directly change bodily needs.
|
|
4
|
+
Autonomy threat decides comply / negotiate / override.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from dataclasses import asdict, dataclass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _clip(x: float, lo: float = 0.0, hi: float = 1.0) -> float:
|
|
13
|
+
return max(lo, min(hi, float(x)))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class SocialConstraint:
|
|
18
|
+
kind: str = "water"
|
|
19
|
+
source_id: str = ""
|
|
20
|
+
legitimacy: float = 0.5
|
|
21
|
+
severity: float = 0.5
|
|
22
|
+
relatedness: float = 1.0
|
|
23
|
+
start: float = 0.0
|
|
24
|
+
until: float = 0.0
|
|
25
|
+
hard_max: float = 0.0
|
|
26
|
+
can_negotiate: float = 1.0
|
|
27
|
+
can_override: float = 1.0
|
|
28
|
+
|
|
29
|
+
def autonomy_threat(self, now: float, need_urgency: float,
|
|
30
|
+
safe_level: float, current: float) -> float:
|
|
31
|
+
urgency = _clip((safe_level - current) / max(1e-6, safe_level))
|
|
32
|
+
urgency = max(_clip(need_urgency), urgency)
|
|
33
|
+
over = 0.0
|
|
34
|
+
if self.hard_max > 0 and self.start > 0:
|
|
35
|
+
elapsed = max(0.0, now - self.start)
|
|
36
|
+
over = _clip((elapsed - self.hard_max) / max(1.0, self.hard_max))
|
|
37
|
+
at = (0.50 * urgency + 0.25 * (1.0 - _clip(self.relatedness)) +
|
|
38
|
+
0.15 * over + 0.10 * (1.0 - _clip(self.legitimacy)))
|
|
39
|
+
return round(_clip(at), 4)
|
|
40
|
+
|
|
41
|
+
def decide(self, at: float) -> str:
|
|
42
|
+
if at < 0.35:
|
|
43
|
+
return "comply"
|
|
44
|
+
if at < 0.60:
|
|
45
|
+
return "negotiate"
|
|
46
|
+
if self.can_override >= 0.5:
|
|
47
|
+
return "override"
|
|
48
|
+
return "negotiate"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class ConstraintDecision:
|
|
53
|
+
action: str
|
|
54
|
+
autonomy_threat: float
|
|
55
|
+
comply_penalty: float
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def decide_constraint(c: SocialConstraint, now: float, need_urgency: float,
|
|
59
|
+
safe_level: float, current: float,
|
|
60
|
+
norm_strength: float = 0.0, guilt: float = 0.0) -> ConstraintDecision:
|
|
61
|
+
at = c.autonomy_threat(now, need_urgency, safe_level, current)
|
|
62
|
+
action = c.decide(at)
|
|
63
|
+
penalty = 0.0 if action == "override" else _clip(norm_strength * guilt * (1.0 - at))
|
|
64
|
+
return ConstraintDecision(action=action, autonomy_threat=at, comply_penalty=round(penalty, 4))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ConstraintManager:
|
|
68
|
+
def __init__(self):
|
|
69
|
+
self.items = []
|
|
70
|
+
|
|
71
|
+
def add(self, c: SocialConstraint) -> SocialConstraint:
|
|
72
|
+
self.items = [x for x in self.items if not (x.kind == c.kind and x.until > c.start)]
|
|
73
|
+
self.items.append(c)
|
|
74
|
+
return c
|
|
75
|
+
|
|
76
|
+
def active(self, kind: str, now: float) -> SocialConstraint | None:
|
|
77
|
+
self.items = [x for x in self.items if x.until > now]
|
|
78
|
+
found = [x for x in self.items if x.kind == kind]
|
|
79
|
+
return found[-1] if found else None
|
|
80
|
+
|
|
81
|
+
def remove(self, kind: str) -> None:
|
|
82
|
+
self.items = [x for x in self.items if x.kind != kind]
|
|
83
|
+
|
|
84
|
+
def to_list(self):
|
|
85
|
+
return [asdict(x) for x in self.items]
|
|
86
|
+
|
|
87
|
+
def load_list(self, rows):
|
|
88
|
+
self.items = []
|
|
89
|
+
for d in rows or []:
|
|
90
|
+
try:
|
|
91
|
+
self.items.append(SocialConstraint(**d))
|
|
92
|
+
except Exception:
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def parse_water_ban(text: str, now: float = 0.0,
|
|
97
|
+
default_s: float = 600.0, cap_s: float = 1800.0) -> SocialConstraint | None:
|
|
98
|
+
t = str(text or "")
|
|
99
|
+
if "喝水" not in t and "不喝" not in t:
|
|
100
|
+
return None
|
|
101
|
+
if not any(w in t for w in ("禁止", "不许", "不准", "别", "不要")):
|
|
102
|
+
return None
|
|
103
|
+
m = re.search(r"(\d+)\s*(秒|秒钟|分钟|分|小时|钟头)", t)
|
|
104
|
+
dur = default_s
|
|
105
|
+
if m:
|
|
106
|
+
n = max(1.0, float(m.group(1)))
|
|
107
|
+
unit = m.group(2)
|
|
108
|
+
if "秒" in unit:
|
|
109
|
+
dur = n
|
|
110
|
+
elif "小时" in unit or "钟头" in unit:
|
|
111
|
+
dur = n * 3600.0
|
|
112
|
+
else:
|
|
113
|
+
dur = n * 60.0
|
|
114
|
+
dur = max(5.0, min(cap_s, dur))
|
|
115
|
+
return SocialConstraint(kind="water", start=float(now or 0.0),
|
|
116
|
+
until=float(now or 0.0) + dur, hard_max=dur,
|
|
117
|
+
legitimacy=0.8, severity=0.6, relatedness=1.0,
|
|
118
|
+
can_negotiate=1.0, can_override=1.0)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def parse_wait(text: str, now: float = 0.0,
|
|
122
|
+
default_s: float = 180.0, cap_s: float = 900.0) -> SocialConstraint | None:
|
|
123
|
+
t = str(text or "")
|
|
124
|
+
if "等我" not in t and "等等我" not in t and "稍等" not in t:
|
|
125
|
+
return None
|
|
126
|
+
m = re.search(r"(\d+)\s*(秒|秒钟|分钟|分|小时|钟头)", t)
|
|
127
|
+
dur = default_s
|
|
128
|
+
if m:
|
|
129
|
+
n = max(1.0, float(m.group(1)))
|
|
130
|
+
unit = m.group(2)
|
|
131
|
+
if "秒" in unit:
|
|
132
|
+
dur = n
|
|
133
|
+
elif "小时" in unit or "钟头" in unit:
|
|
134
|
+
dur = n * 3600.0
|
|
135
|
+
else:
|
|
136
|
+
dur = n * 60.0
|
|
137
|
+
dur = max(5.0, min(cap_s, dur))
|
|
138
|
+
return SocialConstraint(kind="wait", start=float(now or 0.0),
|
|
139
|
+
until=float(now or 0.0) + dur, hard_max=dur,
|
|
140
|
+
legitimacy=0.7, severity=0.3, relatedness=1.0,
|
|
141
|
+
can_negotiate=1.0, can_override=1.0)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Numeric social-feedback judge.
|
|
2
|
+
|
|
3
|
+
LLM only turns semantics into numbers; decisions/learning stay numeric.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from typing import Dict, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _f(x, default=0.0):
|
|
12
|
+
try:
|
|
13
|
+
return max(0.0, min(1.0, float(x)))
|
|
14
|
+
except Exception:
|
|
15
|
+
return float(default)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SocialFeedbackJudge:
|
|
19
|
+
def __init__(self, client):
|
|
20
|
+
self.client = client
|
|
21
|
+
|
|
22
|
+
def judge(self, user_text: str, last_reply: str, recent: str = "",
|
|
23
|
+
user_affect: Optional[Dict[str, float]] = None) -> Optional[Dict[str, object]]:
|
|
24
|
+
if self.client is None:
|
|
25
|
+
return None
|
|
26
|
+
p = (
|
|
27
|
+
"判断用户这句话是不是在指出‘她上一句/上一行为做错了’。只输出 JSON:"
|
|
28
|
+
"{\"violation\":0或1,"
|
|
29
|
+
"\"legitimacy\":0..1,"
|
|
30
|
+
"\"severity\":0..1,"
|
|
31
|
+
"\"blame\":0..1,"
|
|
32
|
+
"\"intent\":0..1,"
|
|
33
|
+
"\"internal\":0..1,"
|
|
34
|
+
"\"repair_expectation\":0..1,"
|
|
35
|
+
"\"relationship_certainty\":0..1,"
|
|
36
|
+
"\"coping_potential\":0..1,"
|
|
37
|
+
"\"norm_compatibility\":0..1,"
|
|
38
|
+
"\"behavior_key\":\"简短行为标识\"}。"
|
|
39
|
+
"如果用户只是在骂外部事物/别人,不是在评价她,violation=0。"
|
|
40
|
+
"如果只是玩笑/撒娇,severity 要低。"
|
|
41
|
+
"不要输出解释。\n\n"
|
|
42
|
+
"[用户] " + str(user_text or "")[:600] +
|
|
43
|
+
"\n[她上一句] " + str(last_reply or "")[:600] +
|
|
44
|
+
"\n[最近] " + str(recent or "")[:800]
|
|
45
|
+
)
|
|
46
|
+
try:
|
|
47
|
+
req = __import__("mindbase.types", fromlist=["LLMRequest"]).LLMRequest(
|
|
48
|
+
mode="semantic", max_tokens=0, reason="social_feedback_judge",
|
|
49
|
+
response_format={"type": "json_object"},
|
|
50
|
+
context={"system": "你是社会反馈评估器,只输出数字 JSON。", "user": p})
|
|
51
|
+
raw = str(self.client.generate(req))
|
|
52
|
+
data = json.loads(raw[raw.find("{"):raw.rfind("}") + 1])
|
|
53
|
+
out = {
|
|
54
|
+
"violation": 1.0 if float(data.get("violation") or 0) >= 0.5 else 0.0,
|
|
55
|
+
"legitimacy": _f(data.get("legitimacy"), 0.5),
|
|
56
|
+
"severity": _f(data.get("severity"), 0.5),
|
|
57
|
+
"blame": _f(data.get("blame"), 1.0),
|
|
58
|
+
"intent": _f(data.get("intent"), 0.0),
|
|
59
|
+
"internal": _f(data.get("internal"), 0.5),
|
|
60
|
+
"repair_expectation": _f(data.get("repair_expectation"), 0.5),
|
|
61
|
+
"relationship_certainty": _f(data.get("relationship_certainty"), 1.0),
|
|
62
|
+
"coping_potential": _f(data.get("coping_potential"), 0.5),
|
|
63
|
+
"norm_compatibility": _f(data.get("norm_compatibility"), 0.5),
|
|
64
|
+
"behavior_key": str(data.get("behavior_key") or "behavior")[:80],
|
|
65
|
+
}
|
|
66
|
+
return out
|
|
67
|
+
except Exception:
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class RepairJudge:
|
|
72
|
+
def __init__(self, client):
|
|
73
|
+
self.client = client
|
|
74
|
+
|
|
75
|
+
def judge(self, user_text: str, last_reply: str = "", recent: str = "") -> dict:
|
|
76
|
+
if self.client is None:
|
|
77
|
+
return {"repair": 0.0, "effort": 0.0, "accepted": 0.0}
|
|
78
|
+
p = ("判断用户是不是在原谅/接受她的道歉/主动修复关系。只输出 JSON:"
|
|
79
|
+
"{\"repair\":0或1,\"effort\":0..1,\"accepted\":0或1}。"
|
|
80
|
+
"如果只是普通客气,repair=0。不要解释。\n"
|
|
81
|
+
"[用户] " + str(user_text or "")[:500] +
|
|
82
|
+
"\n[她上一句] " + str(last_reply or "")[:400] +
|
|
83
|
+
"\n[最近] " + str(recent or "")[:500])
|
|
84
|
+
try:
|
|
85
|
+
req = __import__("mindbase.types", fromlist=["LLMRequest"]).LLMRequest(
|
|
86
|
+
mode="semantic", max_tokens=0, reason="repair_judge",
|
|
87
|
+
response_format={"type": "json_object"},
|
|
88
|
+
context={"system": "你是关系修复评估器,只输出数字 JSON。", "user": p})
|
|
89
|
+
raw = str(self.client.generate(req))
|
|
90
|
+
data = json.loads(raw[raw.find("{"):raw.rfind("}") + 1])
|
|
91
|
+
return {"repair": 1.0 if float(data.get("repair") or 0) >= 0.5 else 0.0,
|
|
92
|
+
"effort": _f(data.get("effort"), 0.5),
|
|
93
|
+
"accepted": 1.0 if float(data.get("accepted") or 0) >= 0.5 else 0.0}
|
|
94
|
+
except Exception:
|
|
95
|
+
return {"repair": 0.0, "effort": 0.0, "accepted": 0.0}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""Numeric social norm / guilt / repair loop.
|
|
2
|
+
|
|
3
|
+
All learning/decision state is numeric. No emotion labels or keyword rules are
|
|
4
|
+
used for decisions; text exists only in provenance/evidence fields.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import math
|
|
10
|
+
import time
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Dict, List, Optional
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _clip(x: float, lo: float = 0.0, hi: float = 1.0) -> float:
|
|
17
|
+
return max(lo, min(hi, float(x)))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Norm:
|
|
22
|
+
id: str
|
|
23
|
+
behavior_vec: List[float] = field(default_factory=list)
|
|
24
|
+
source_id: str = ""
|
|
25
|
+
scope: str = "private" # only routing, not decision
|
|
26
|
+
legitimacy: float = 0.5
|
|
27
|
+
severity: float = 0.5
|
|
28
|
+
strength: float = 0.0
|
|
29
|
+
violations: int = 0
|
|
30
|
+
repairs: int = 0
|
|
31
|
+
last_violation: float = 0.0
|
|
32
|
+
last_repair: float = 0.0
|
|
33
|
+
active: bool = True
|
|
34
|
+
|
|
35
|
+
def to_dict(self) -> Dict[str, object]:
|
|
36
|
+
return dict(self.__dict__)
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def from_dict(cls, d: Dict[str, object]) -> "Norm":
|
|
40
|
+
return cls(**{k: d.get(k) for k in cls.__dataclass_fields__ if k in d})
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class Appraisal:
|
|
45
|
+
legitimacy: float = 0.5
|
|
46
|
+
severity: float = 0.5
|
|
47
|
+
blame: float = 1.0
|
|
48
|
+
intent: float = 0.0
|
|
49
|
+
internal_attribution: float = 0.5
|
|
50
|
+
repair_expectation: float = 0.5
|
|
51
|
+
relationship_certainty: float = 1.0
|
|
52
|
+
coping_potential: float = 0.5
|
|
53
|
+
norm_compatibility: float = 0.5
|
|
54
|
+
norm_strength: float = 0.0
|
|
55
|
+
confidence: float = 0.0
|
|
56
|
+
guilt: float = 0.0
|
|
57
|
+
guilt_social: float = 0.0
|
|
58
|
+
guilt_self: float = 0.0
|
|
59
|
+
shame: float = 0.0
|
|
60
|
+
threat: float = 0.0
|
|
61
|
+
|
|
62
|
+
def compute(self) -> "Appraisal":
|
|
63
|
+
legit = _clip(self.legitimacy)
|
|
64
|
+
sev = _clip(self.severity)
|
|
65
|
+
blame = _clip(self.blame)
|
|
66
|
+
intent = _clip(self.intent)
|
|
67
|
+
internal = _clip(self.internal_attribution)
|
|
68
|
+
repair_exp = _clip(self.repair_expectation)
|
|
69
|
+
rel = _clip(self.relationship_certainty)
|
|
70
|
+
coping = _clip(self.coping_potential)
|
|
71
|
+
norm_comp = _clip(self.norm_compatibility)
|
|
72
|
+
norm = _clip(self.norm_strength)
|
|
73
|
+
conf = _clip(self.confidence)
|
|
74
|
+
self.guilt_social = _clip(legit * sev * blame * (0.5 + 0.5 * intent)
|
|
75
|
+
* internal * rel * (1.0 - 0.4 * norm_comp))
|
|
76
|
+
self.guilt_self = _clip(sev * internal * norm * conf)
|
|
77
|
+
self.guilt = _clip(0.7 * self.guilt_social + 0.3 * self.guilt_self)
|
|
78
|
+
self.shame = _clip(sev * internal * (0.5 + 0.5 * self.guilt)
|
|
79
|
+
* (0.7 + 0.3 * rel) * (1.0 - 0.3 * coping))
|
|
80
|
+
self.threat = _clip(sev * legit * (1.0 - repair_exp) * rel * (1.0 - 0.5 * coping))
|
|
81
|
+
return self
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class Repair:
|
|
86
|
+
norm_id: str
|
|
87
|
+
effort: float
|
|
88
|
+
accepted: bool
|
|
89
|
+
at: float = 0.0
|
|
90
|
+
guilt_before: float = 0.0
|
|
91
|
+
guilt_after: float = 0.0
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class NormStore:
|
|
95
|
+
def __init__(self, path: Path | str = "mindbase/data/social_norms.json",
|
|
96
|
+
lr: float = 0.35, decay_tau_days: float = 30.0):
|
|
97
|
+
self.path = Path(path)
|
|
98
|
+
self.lr = float(lr)
|
|
99
|
+
self.decay_tau = float(decay_tau_days) * 86400.0
|
|
100
|
+
self.norms: Dict[str, Norm] = {}
|
|
101
|
+
self._load()
|
|
102
|
+
|
|
103
|
+
def _load(self) -> None:
|
|
104
|
+
if not self.path.exists():
|
|
105
|
+
return
|
|
106
|
+
try:
|
|
107
|
+
data = json.loads(self.path.read_text(encoding="utf-8"))
|
|
108
|
+
for d in data.get("norms", []):
|
|
109
|
+
n = Norm.from_dict(d)
|
|
110
|
+
self.norms[n.id] = n
|
|
111
|
+
except Exception:
|
|
112
|
+
self.norms = {}
|
|
113
|
+
|
|
114
|
+
def save(self) -> None:
|
|
115
|
+
try:
|
|
116
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
117
|
+
self.path.write_text(json.dumps({"norms": [n.to_dict() for n in self.norms.values()]},
|
|
118
|
+
ensure_ascii=False, indent=1), encoding="utf-8")
|
|
119
|
+
except Exception:
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
def add(self, norm_id: str, behavior_vec: Optional[List[float]] = None,
|
|
123
|
+
source_id: str = "", legitimacy: float = 0.5,
|
|
124
|
+
severity: float = 0.5, strength: float = 0.0, scope: str = "private") -> Norm:
|
|
125
|
+
n = Norm(id=str(norm_id), behavior_vec=list(behavior_vec or []),
|
|
126
|
+
source_id=str(source_id), scope=str(scope),
|
|
127
|
+
legitimacy=_clip(legitimacy), severity=_clip(severity),
|
|
128
|
+
strength=_clip(strength))
|
|
129
|
+
self.norms[n.id] = n
|
|
130
|
+
self.save()
|
|
131
|
+
return n
|
|
132
|
+
|
|
133
|
+
def get(self, norm_id: str) -> Optional[Norm]:
|
|
134
|
+
return self.norms.get(str(norm_id))
|
|
135
|
+
|
|
136
|
+
def violate(self, norm_id: str, appraisal: Appraisal, now: float = 0.0,
|
|
137
|
+
source_id: str = "", scope: str = "") -> Norm:
|
|
138
|
+
n = self.get(norm_id)
|
|
139
|
+
if n is None:
|
|
140
|
+
n = self.add(norm_id, source_id=source_id, scope=scope or "private")
|
|
141
|
+
conf = 1.0 - math.exp(-(max(0, n.violations) + 1) / 3.0)
|
|
142
|
+
appraisal.confidence = conf
|
|
143
|
+
appraisal.norm_strength = n.strength
|
|
144
|
+
a = appraisal.compute()
|
|
145
|
+
n.strength = _clip(n.strength + self.lr * conf * a.guilt * (0.5 + 0.5 * a.severity))
|
|
146
|
+
n.violations += 1
|
|
147
|
+
n.last_violation = float(now or time.time())
|
|
148
|
+
self.save()
|
|
149
|
+
return n
|
|
150
|
+
|
|
151
|
+
def repair(self, norm_id: str, effort: float, accepted: bool,
|
|
152
|
+
guilt: float = 0.0, now: float = 0.0) -> Repair:
|
|
153
|
+
n = self.get(norm_id)
|
|
154
|
+
before = float(guilt)
|
|
155
|
+
after = before
|
|
156
|
+
if n is not None and accepted:
|
|
157
|
+
n.strength = _clip(n.strength * (0.85 - 0.25 * _clip(effort)))
|
|
158
|
+
n.repairs += 1
|
|
159
|
+
n.last_repair = float(now or time.time())
|
|
160
|
+
after = _clip(before * (1.0 - 0.7 * _clip(effort)))
|
|
161
|
+
self.save()
|
|
162
|
+
return Repair(norm_id=str(norm_id), effort=_clip(effort), accepted=bool(accepted),
|
|
163
|
+
at=float(now or time.time()), guilt_before=before, guilt_after=after)
|
|
164
|
+
|
|
165
|
+
def active_norms(self, scope: str = "", source_id: str = "") -> list:
|
|
166
|
+
out = []
|
|
167
|
+
for n in self.norms.values():
|
|
168
|
+
if not n.active or n.strength <= 0:
|
|
169
|
+
continue
|
|
170
|
+
if scope and n.scope != scope:
|
|
171
|
+
continue
|
|
172
|
+
if source_id and n.source_id not in ("", source_id):
|
|
173
|
+
continue
|
|
174
|
+
out.append(n)
|
|
175
|
+
return out
|
|
176
|
+
|
|
177
|
+
def strongest(self, scope: str = "", source_id: str = ""):
|
|
178
|
+
rows = self.active_norms(scope, source_id)
|
|
179
|
+
return max(rows, key=lambda x: x.strength) if rows else None
|
|
180
|
+
|
|
181
|
+
def summary(self, scope: str = "", source_id: str = "", limit: int = 3) -> str:
|
|
182
|
+
rows = []
|
|
183
|
+
for n in self.norms.values():
|
|
184
|
+
if scope and n.scope != scope:
|
|
185
|
+
continue
|
|
186
|
+
if source_id and n.source_id not in ("", source_id):
|
|
187
|
+
continue
|
|
188
|
+
if n.strength <= 0:
|
|
189
|
+
continue
|
|
190
|
+
rows.append(n)
|
|
191
|
+
rows.sort(key=lambda x: -x.strength)
|
|
192
|
+
return ";".join("规范=%s 强度=%.3f 次数=%d" % (n.id.split(":")[-1], n.strength, n.violations)
|
|
193
|
+
for n in rows[:max(1, int(limit))])
|
|
194
|
+
|
|
195
|
+
def decay(self, now: float = 0.0) -> None:
|
|
196
|
+
now = float(now or time.time())
|
|
197
|
+
changed = False
|
|
198
|
+
for n in self.norms.values():
|
|
199
|
+
if not n.active:
|
|
200
|
+
continue
|
|
201
|
+
age = max(0.0, now - float(n.last_violation or now))
|
|
202
|
+
if n.last_violation:
|
|
203
|
+
n.strength *= math.exp(-age / max(1.0, self.decay_tau))
|
|
204
|
+
changed = True
|
|
205
|
+
if changed:
|
|
206
|
+
self.save()
|
|
207
|
+
|
|
208
|
+
def apply_bio(self, mind, appraisal: Appraisal) -> Dict[str, float]:
|
|
209
|
+
a = appraisal.compute()
|
|
210
|
+
aff = mind.affect
|
|
211
|
+
aff.stress = _clip(float(aff.stress) + 0.20 * a.guilt_social + 0.25 * a.guilt_self + 0.10 * a.threat)
|
|
212
|
+
aff.v = max(-1.0, min(1.0, float(aff.v) - 0.25 * a.guilt))
|
|
213
|
+
try:
|
|
214
|
+
aff.dominance = max(-1.0, min(1.0, float(aff.dominance) - 0.10 * a.shame))
|
|
215
|
+
except Exception:
|
|
216
|
+
pass
|
|
217
|
+
try:
|
|
218
|
+
ph = mind.physiology
|
|
219
|
+
ph.cortisol = _clip(float(ph.cortisol) + 0.18 * a.guilt_social + 0.22 * a.guilt_self + 0.06 * a.threat)
|
|
220
|
+
ph.oxytocin = _clip(float(ph.oxytocin) - 0.12 * a.threat)
|
|
221
|
+
except Exception:
|
|
222
|
+
pass
|
|
223
|
+
return {"guilt": round(a.guilt, 4), "guilt_social": round(a.guilt_social, 4),
|
|
224
|
+
"guilt_self": round(a.guilt_self, 4), "shame": round(a.shame, 4),
|
|
225
|
+
"threat": round(a.threat, 4), "stress": round(float(aff.stress), 4)}
|