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,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import random
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
from .noise import clip, ou
|
|
7
|
+
from .types import Appraisal, Event
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SelfModel:
|
|
11
|
+
"""Minimal self: agency, continuity, consistency, identity.
|
|
12
|
+
|
|
13
|
+
identity is a slow variable: contradictions move it down, coherent
|
|
14
|
+
experience lets it recover. This is operational, not a consciousness claim.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, rng: random.Random):
|
|
18
|
+
self.rng = rng
|
|
19
|
+
self.agency = 0.50
|
|
20
|
+
self.continuity = 0.50
|
|
21
|
+
self.consistency = 0.80
|
|
22
|
+
self.identity = 0.70
|
|
23
|
+
self.value_stability = 1.0
|
|
24
|
+
self.feedback_expectation = 0.0
|
|
25
|
+
self.identity_prediction = self.identity
|
|
26
|
+
self.identity_setpoint_base = 0.70
|
|
27
|
+
self.prediction_error = 0.0
|
|
28
|
+
# allostatic self-efficacy: can I reduce the current setpoint error?
|
|
29
|
+
self.allostatic_self_efficacy = 0.50
|
|
30
|
+
self.expression_confidence = 0.50
|
|
31
|
+
self.identity_gain = 1.0
|
|
32
|
+
|
|
33
|
+
def on_event(self, ev: Event, ap: Appraisal) -> None:
|
|
34
|
+
if ev.speaker == "self":
|
|
35
|
+
self.agency = clip(self.agency + 0.03, 0.0, 1.0)
|
|
36
|
+
self.continuity = clip(self.continuity + 0.01, 0.0, 1.0)
|
|
37
|
+
else:
|
|
38
|
+
self.agency = clip(self.agency - 0.005, 0.0, 1.0)
|
|
39
|
+
self.continuity = clip(self.continuity + 0.002, 0.0, 1.0)
|
|
40
|
+
self.consistency = clip(
|
|
41
|
+
0.95 * self.consistency + 0.05 * (1.0 - abs(ap.valence) * 0.2),
|
|
42
|
+
0.0,
|
|
43
|
+
1.0,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def on_action(self, action: str, spoke: bool) -> None:
|
|
47
|
+
target = 1.0 if spoke else 0.35
|
|
48
|
+
self.agency = ou(self.agency, target, 1.0 / 60.0, 0.05, 1.0, self.rng)
|
|
49
|
+
self.continuity = clip(self.continuity + (0.002 if spoke else 0.0), 0.0, 1.0)
|
|
50
|
+
self.identity = clip(self.identity + (0.001 if spoke else -0.0005) * float(getattr(self, "identity_gain", 1.0)), 0.0, 1.0)
|
|
51
|
+
|
|
52
|
+
def tick(self, dt: float) -> None:
|
|
53
|
+
rate = min(1.0, max(0.0, dt) / (7.0 * 86400.0))
|
|
54
|
+
# value stability recovers when contradictions stop arriving
|
|
55
|
+
self.value_stability = clip(
|
|
56
|
+
self.value_stability + (1.0 - self.value_stability) * rate, 0.0, 1.0
|
|
57
|
+
)
|
|
58
|
+
target = clip(
|
|
59
|
+
0.50 * self.continuity + 0.30 * self.consistency + 0.20 * self.value_stability,
|
|
60
|
+
0.0,
|
|
61
|
+
1.0,
|
|
62
|
+
)
|
|
63
|
+
self.identity = clip(self.identity + (target - self.identity) * rate, 0.0, 1.0)
|
|
64
|
+
# predictive self: predict before observing, then update from error
|
|
65
|
+
pred_err = self.identity - self.identity_prediction
|
|
66
|
+
self.identity_prediction = clip(self.identity_prediction + 0.20 * pred_err, 0.0, 1.0)
|
|
67
|
+
self.prediction_error = pred_err
|
|
68
|
+
|
|
69
|
+
def on_expression(self, metrics: Dict[str, float]) -> None:
|
|
70
|
+
length = float(metrics.get("length", 0.0))
|
|
71
|
+
question = float(metrics.get("question", 0.0))
|
|
72
|
+
diversity = float(metrics.get("diversity", 0.0))
|
|
73
|
+
novelty = float(metrics.get("novelty", 0.0))
|
|
74
|
+
quality = clip(0.30 * min(1.0, length / 80.0) + 0.25 * diversity + 0.30 * novelty + 0.15 * question, 0.0, 1.0)
|
|
75
|
+
self.expression_confidence = clip(0.90 * self.expression_confidence + 0.10 * quality, 0.0, 1.0)
|
|
76
|
+
self.identity = clip(self.identity + 0.002 * (quality - 0.50) * float(getattr(self, "identity_gain", 1.0)), 0.0, 1.0)
|
|
77
|
+
|
|
78
|
+
def on_feedback(self, value_delta: Dict[str, float], conflict: str = "none", signal: float = 0.0) -> None:
|
|
79
|
+
# Unified RPE: fly DAN and human dopamine both encode reward prediction error.
|
|
80
|
+
sig = clip(float(signal), -1.0, 1.0)
|
|
81
|
+
rpe = sig - self.feedback_expectation
|
|
82
|
+
self.feedback_expectation = clip(self.feedback_expectation + 0.20 * rpe, -1.0, 1.0)
|
|
83
|
+
# value/identity are slow integrators of the same prediction error
|
|
84
|
+
self.value_stability = clip(self.value_stability + 0.06 * rpe, 0.0, 1.0)
|
|
85
|
+
# self-verification: identity is an attractor around a feedback-dependent setpoint
|
|
86
|
+
self.identity_setpoint = clip(float(getattr(self, "identity_setpoint_base", 0.70)) + 0.25 * self.feedback_expectation, 0.30, 0.92)
|
|
87
|
+
gain = float(getattr(self, "identity_gain", 1.0))
|
|
88
|
+
self.identity = clip(self.identity + gain * (0.05 * (self.identity_setpoint - self.identity) + 0.02 * rpe), 0.0, 1.0)
|
|
89
|
+
# goal conflict is an additional aversive prediction error, not a hand-tuned valence
|
|
90
|
+
if conflict == "high":
|
|
91
|
+
self.identity = clip(self.identity - 0.01, 0.0, 1.0)
|
|
92
|
+
|
|
93
|
+
def update_allostatic_efficacy(self, load: float, confidence: float, anxiety: float = 0.0, social_load: float = 0.0, physiological_load: float = 0.0, safety_load: float = 0.0, social_support: float = 0.0) -> None:
|
|
94
|
+
ax = clip(float(anxiety), 0.0, 1.0)
|
|
95
|
+
# social baseline: social load weighs more under anxious attachment
|
|
96
|
+
social_weight = (0.40 + 0.40 * ax) * (1.0 - 0.30 * clip(float(social_support), 0.0, 1.0))
|
|
97
|
+
weighted = 0.40 * float(physiological_load) + social_weight * float(social_load) + 0.40 * float(safety_load)
|
|
98
|
+
load = clip(max(float(load), weighted), 0.0, 1.0)
|
|
99
|
+
conf = clip(float(confidence) * (1.0 - 0.30 * ax), 0.0, 1.0)
|
|
100
|
+
self.ase_components = {"load": round(load, 4), "confidence": round(conf, 4), "anxiety": round(clip(float(anxiety), 0.0, 1.0), 4)}
|
|
101
|
+
self.allostatic_self_efficacy = clip(conf * (1.0 - load), 0.0, 1.0)
|
|
102
|
+
|
|
103
|
+
def snapshot(self) -> Dict[str, float]:
|
|
104
|
+
return {
|
|
105
|
+
"agency": round(self.agency, 3),
|
|
106
|
+
"continuity": round(self.continuity, 3),
|
|
107
|
+
"consistency": round(self.consistency, 3),
|
|
108
|
+
"identity": round(self.identity, 3),
|
|
109
|
+
"value_stability": round(self.value_stability, 3),
|
|
110
|
+
"feedback_expectation": round(self.feedback_expectation, 3),
|
|
111
|
+
"identity_setpoint": round(getattr(self, "identity_setpoint", 0.70), 3),
|
|
112
|
+
"identity_prediction": round(self.identity_prediction, 3),
|
|
113
|
+
"prediction_error": round(self.prediction_error, 4),
|
|
114
|
+
"allostatic_self_efficacy": round(self.allostatic_self_efficacy, 3),
|
|
115
|
+
"ase_components": dict(getattr(self, "ase_components", {})),
|
|
116
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .config import MindConfig
|
|
4
|
+
from .engine import MindBase
|
|
5
|
+
from .types import Event
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> None:
|
|
9
|
+
cfg = MindConfig(seed=7, autonomy_threshold=0.05, abstain_threshold=0.30)
|
|
10
|
+
mb = MindBase(cfg)
|
|
11
|
+
|
|
12
|
+
events = [
|
|
13
|
+
Event(text="你好", speaker="A", ts=0),
|
|
14
|
+
Event(text="你今天真棒", speaker="A", ts=60),
|
|
15
|
+
Event(text="你真废物", speaker="B", ts=120),
|
|
16
|
+
Event(text="我今天好累", speaker="A", ts=180),
|
|
17
|
+
Event(text="晚安", speaker="B", ts=240),
|
|
18
|
+
Event(kind="tick", ts=300),
|
|
19
|
+
Event(text="在吗", speaker="A", directed=True, ts=360),
|
|
20
|
+
]
|
|
21
|
+
outs = mb.run(events)
|
|
22
|
+
|
|
23
|
+
mb.assert_invariants()
|
|
24
|
+
snap = mb.snapshot()
|
|
25
|
+
assert "affect" in snap and "drives" in snap and "memory" in snap
|
|
26
|
+
v = snap["affect"]["v"]
|
|
27
|
+
a = snap["affect"]["a"]
|
|
28
|
+
stress = snap["affect"]["stress"]
|
|
29
|
+
assert -1.0 <= v <= 1.0, v
|
|
30
|
+
assert 0.0 <= a <= 1.0, a
|
|
31
|
+
assert 0.0 <= stress <= 1.0, stress
|
|
32
|
+
|
|
33
|
+
assert any("废物" in item.text for item in mb.memory.items), "memory did not write"
|
|
34
|
+
assert len(mb.wm.slots) <= cfg.wm_capacity
|
|
35
|
+
assert outs[-1].action == "speak" and outs[-1].reason == "directed"
|
|
36
|
+
assert outs[-1].llm_request is not None and outs[-1].llm_request.mode == "reply"
|
|
37
|
+
|
|
38
|
+
# force an endogenous action with no external trigger
|
|
39
|
+
mb.last_speaker = None
|
|
40
|
+
mb.autonomy.h = 10.0
|
|
41
|
+
mb.autonomy.last_spoke = -1e9
|
|
42
|
+
mb.will.willpower = 1.0
|
|
43
|
+
proactive = mb.tick(60)
|
|
44
|
+
assert proactive.action == "speak" and proactive.reason in ("impulse", "intention"), proactive
|
|
45
|
+
assert proactive.llm_request is not None and proactive.llm_request.mode == "proactive", proactive.llm_request
|
|
46
|
+
|
|
47
|
+
# intention can veto impulse when willpower is high
|
|
48
|
+
mb.intentions.add("stay quiet", action="silence", strength=0.9, commitment=0.9, now=mb.t)
|
|
49
|
+
mb.will.willpower = 1.0
|
|
50
|
+
mb.autonomy.h = 10.0
|
|
51
|
+
mb.autonomy.last_spoke = -1e9
|
|
52
|
+
veto = mb.tick(60)
|
|
53
|
+
assert veto.action == "silence" and veto.reason == "intention", veto
|
|
54
|
+
assert veto.llm_request is None, veto.llm_request
|
|
55
|
+
|
|
56
|
+
# depleted willpower gives in to impulse
|
|
57
|
+
mb.will.willpower = 0.0
|
|
58
|
+
mb.autonomy.h = 10.0
|
|
59
|
+
mb.autonomy.last_spoke = -1e9
|
|
60
|
+
give_in = mb.tick(60)
|
|
61
|
+
assert give_in.action == "speak" and give_in.reason == "impulse", give_in
|
|
62
|
+
|
|
63
|
+
# mushroom body: reward increases prediction; no feedback does not
|
|
64
|
+
p0, _ = mb.mb.predict("奖励线索")
|
|
65
|
+
mb.post_reply("奖励线索", feedback=1.0)
|
|
66
|
+
p1, _ = mb.mb.predict("奖励线索")
|
|
67
|
+
assert p1 > p0, (p0, p1)
|
|
68
|
+
|
|
69
|
+
p2, _ = mb.mb.predict("中性线索")
|
|
70
|
+
mb.post_reply("中性线索", feedback=None)
|
|
71
|
+
p3, _ = mb.mb.predict("中性线索")
|
|
72
|
+
assert abs(p3 - p2) < 1e-9, (p2, p3)
|
|
73
|
+
|
|
74
|
+
# two channels: positive feedback -> reward only
|
|
75
|
+
b = mb.mb.predict_channels("正性线索")
|
|
76
|
+
mb.post_reply("正性线索", feedback=1.0)
|
|
77
|
+
a = mb.mb.predict_channels("正性线索")
|
|
78
|
+
assert a["reward"] > b["reward"], (b, a)
|
|
79
|
+
assert abs(a["punishment"] - b["punishment"]) < 1e-9, (b, a)
|
|
80
|
+
|
|
81
|
+
# negative feedback -> punishment only, value drops
|
|
82
|
+
b = mb.mb.predict_channels("负性线索")
|
|
83
|
+
mb.post_reply("负性线索", feedback=-1.0)
|
|
84
|
+
a = mb.mb.predict_channels("负性线索")
|
|
85
|
+
assert a["punishment"] > b["punishment"], (b, a)
|
|
86
|
+
assert a["value"] < b["value"], (b, a)
|
|
87
|
+
|
|
88
|
+
# APL feedback loop: repeated high density -> lower gain
|
|
89
|
+
g0 = mb.mb.apl.step(list(range(64)), mb.mb.kc.n, 0.05)
|
|
90
|
+
for _ in range(5):
|
|
91
|
+
g1 = mb.mb.apl.step(list(range(64)), mb.mb.kc.n, 0.05)
|
|
92
|
+
assert g1 < g0, (g0, g1)
|
|
93
|
+
|
|
94
|
+
# homeostatic scaling: total absolute weight bounded
|
|
95
|
+
mb.mb.mbon_reward.w = [1.0] * mb.mb.kc.n
|
|
96
|
+
mb.mb.homeostatic(10.0)
|
|
97
|
+
assert sum(abs(w) for w in mb.mb.mbon_reward.w) <= 10.001
|
|
98
|
+
|
|
99
|
+
# habituation: repeated cue -> weaker; negative -> sensitize
|
|
100
|
+
h0 = mb.mb.hab.response("重复线索")
|
|
101
|
+
mb.learner.on_event("重复线索", 0.0)
|
|
102
|
+
h1 = mb.mb.hab.response("重复线索")
|
|
103
|
+
assert h1 < h0, (h0, h1)
|
|
104
|
+
mb.learner.on_event("重复线索", -1.0)
|
|
105
|
+
h2 = mb.mb.hab.response("重复线索")
|
|
106
|
+
assert h2 > h1, (h1, h2)
|
|
107
|
+
|
|
108
|
+
silent = mb.step(Event(kind="self_action", speaker="self", ts=mb.t + 60))
|
|
109
|
+
assert silent.action == "silence" and silent.llm_request is None, silent
|
|
110
|
+
|
|
111
|
+
mb.assert_invariants()
|
|
112
|
+
print("mindbase selftest: OK")
|
|
113
|
+
print("events:", len(outs), "| memory items:", len(mb.memory.items), "| wm:", len(mb.wm.slots))
|
|
114
|
+
print("last state:", mb.prompt_state(proactive.action, proactive.reason))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
main()
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from typing import Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
from .types import LLMRequest
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _json(text: str) -> Dict[str, object]:
|
|
11
|
+
text = str(text or "").strip()
|
|
12
|
+
start, end = text.find("{"), text.rfind("}")
|
|
13
|
+
if start >= 0 and end > start:
|
|
14
|
+
text = text[start:end + 1]
|
|
15
|
+
try:
|
|
16
|
+
obj = json.loads(text)
|
|
17
|
+
return obj if isinstance(obj, dict) else {}
|
|
18
|
+
except Exception:
|
|
19
|
+
return {}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SemanticMemory:
|
|
23
|
+
"""LLM-based fact extraction / memory question / evidence judge. No keyword tables."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, client):
|
|
26
|
+
self.client = client
|
|
27
|
+
|
|
28
|
+
def _ask(self, system: str, user: str, max_tokens: int = 300) -> Dict[str, object]:
|
|
29
|
+
req = LLMRequest(mode="semantic", urgency=0.5, max_tokens=max_tokens, reason="semantic",
|
|
30
|
+
context={"system": system, "user": user})
|
|
31
|
+
raw = self.client.generate(req)
|
|
32
|
+
self.last_raw = raw
|
|
33
|
+
return _json(raw)
|
|
34
|
+
|
|
35
|
+
def parse(self, text: str) -> Dict[str, object]:
|
|
36
|
+
out = self._ask(
|
|
37
|
+
"你只做信息抽取。只输出 JSON,不要解释。",
|
|
38
|
+
"用户说:" + str(text) +
|
|
39
|
+
"\n返回 JSON:{\"is_fact\": true/false, \"fact\": \"关于用户的事实,没有则空\", "
|
|
40
|
+
"\"is_memory_question\": true/false, \"query\": \"如果是在问记忆,提取查询关键词/实体,否则空\"}",
|
|
41
|
+
220,
|
|
42
|
+
)
|
|
43
|
+
return {
|
|
44
|
+
"is_fact": bool(out.get("is_fact")),
|
|
45
|
+
"fact": str(out.get("fact") or "").strip(),
|
|
46
|
+
"is_memory_question": bool(out.get("is_memory_question")),
|
|
47
|
+
"query": str(out.get("query") or "").strip(),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
def reply(self, text: str, memories: List[str], persona: str = "", recent: Optional[List[str]] = None, insight: str = "", state: str = "", diary: str = "", preference: str = "", emotion: str = "", noise: float = 0.0) -> Dict[str, object]:
|
|
51
|
+
system = (persona.strip() + "\n\n" if persona else "") + (
|
|
52
|
+
"你在做一次完整的社交回复。只输出 JSON,不要 markdown,不要解释。"
|
|
53
|
+
"如果对方在问记忆,只能用给出的记忆回答;记忆不支持就诚实说不知道,不要编。"
|
|
54
|
+
)
|
|
55
|
+
user = (
|
|
56
|
+
"最近对话:\n" + "\n".join("- " + str(x) for x in (recent or [])[-4:]) +
|
|
57
|
+
"\n记忆:\n" + "\n".join("- " + str(m) for m in memories[:6]) +
|
|
58
|
+
("\n你现在的身体/情绪状态:" + str(state) if state else "") +
|
|
59
|
+
("\n你最近的日记(可参考,不要照念):" + str(diary)[:400] if diary else "") +
|
|
60
|
+
("\n这个用户的表达偏好:" + str(preference) if preference else "") +
|
|
61
|
+
("\n你当前的需要和情绪:" + str(emotion) if emotion else "") +
|
|
62
|
+
("\n轻微情绪噪声:" + str(round(float(noise), 3)) if noise else "") +
|
|
63
|
+
("\n你刚冒出的念头(可自然带出,不强制,不要复述):" + str(insight) if insight else "") +
|
|
64
|
+
"\n用户刚说:" + str(text) +
|
|
65
|
+
"\n返回 JSON:{\"reply\": \"对用户说的一句中文,1-3句\", \"facts\": [\"关于用户的新事实,没有则空数组\"], \"memory_used\": \"用到的记忆,没有则空\", "
|
|
66
|
+
"\"appraisal\": {\"valence\": -1到1, \"need_delta\": -1到1, \"fear_delta\": -1到1, \"joy_delta\": -1到1, "
|
|
67
|
+
"\"agency\": 0到1, \"certainty\": 0到1, \"control\": 0到1, \"novelty\": 0到1}}"
|
|
68
|
+
)
|
|
69
|
+
raw = ""
|
|
70
|
+
try:
|
|
71
|
+
req = LLMRequest(mode="semantic", urgency=0.5, max_tokens=320, reason="semantic_reply",
|
|
72
|
+
context={"system": system, "user": user})
|
|
73
|
+
raw = str(self.client.generate(req) or "")
|
|
74
|
+
out = _json(raw)
|
|
75
|
+
except Exception:
|
|
76
|
+
out = {}
|
|
77
|
+
reply = str(out.get("reply") or "").strip()
|
|
78
|
+
if not reply:
|
|
79
|
+
reply = raw.strip()
|
|
80
|
+
facts = out.get("facts") or []
|
|
81
|
+
if isinstance(facts, str):
|
|
82
|
+
facts = [facts]
|
|
83
|
+
if not isinstance(facts, list):
|
|
84
|
+
facts = []
|
|
85
|
+
appraisal = out.get("appraisal") or {}
|
|
86
|
+
if not isinstance(appraisal, dict):
|
|
87
|
+
appraisal = {}
|
|
88
|
+
return {"reply": reply, "facts": [str(x) for x in facts if str(x).strip()], "memory_used": str(out.get("memory_used") or ""), "appraisal": appraisal}
|
|
89
|
+
|
|
90
|
+
def sentiment(self, text: str) -> float:
|
|
91
|
+
out = self._ask("判断用户这句话的情绪,只输出 JSON。", "用户说:" + str(text) + "\n返回 JSON:{\"sentiment\": -1 到 1 的数}", 300)
|
|
92
|
+
try:
|
|
93
|
+
return max(-1.0, min(1.0, float(out.get("sentiment", 0.0))))
|
|
94
|
+
except Exception:
|
|
95
|
+
return 0.0
|
|
96
|
+
|
|
97
|
+
def judge(self, question: str, memories: List[str]) -> Optional[str]:
|
|
98
|
+
out = self._ask(
|
|
99
|
+
"你只根据给出的记忆回答。没有证据就诚实说不知道,不要编。只输出 JSON。",
|
|
100
|
+
"问题:" + str(question) +
|
|
101
|
+
"\n记忆:\n" + "\n".join("- " + str(m) for m in memories[:6]) +
|
|
102
|
+
"\n返回 JSON:{\"supported\": true/false, \"answer\": \"用一句中文回答;supported=false 时写 不知道\"}",
|
|
103
|
+
240,
|
|
104
|
+
)
|
|
105
|
+
if not out.get("supported"):
|
|
106
|
+
return None
|
|
107
|
+
answer = str(out.get("answer") or "").strip()
|
|
108
|
+
return answer or None
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict
|
|
7
|
+
|
|
8
|
+
from .memory import MemoryItem
|
|
9
|
+
from .social import Bond
|
|
10
|
+
import numpy as np
|
|
11
|
+
from .fly_mb import FlyMushroomBody
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SessionStore:
|
|
15
|
+
"""Persist per-user session: facts, turns, identity, drives, affect."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, root: Path):
|
|
18
|
+
self.root = Path(root)
|
|
19
|
+
self.root.mkdir(parents=True, exist_ok=True)
|
|
20
|
+
|
|
21
|
+
def _path(self, user_id: str) -> Path:
|
|
22
|
+
key = hashlib.sha256(str(user_id).encode("utf-8")).hexdigest()[:16]
|
|
23
|
+
return self.root / (key + ".json")
|
|
24
|
+
|
|
25
|
+
def _fly_path(self, user_id: str) -> Path:
|
|
26
|
+
key = hashlib.sha256(str(user_id).encode("utf-8")).hexdigest()[:16]
|
|
27
|
+
return self.root / (key + ".fly.npz")
|
|
28
|
+
|
|
29
|
+
def save(self, user_id: str, session) -> None:
|
|
30
|
+
try:
|
|
31
|
+
m = session.mind
|
|
32
|
+
facts = []
|
|
33
|
+
for item in list(m.memory.semantic.items)[-200:]:
|
|
34
|
+
facts.append({
|
|
35
|
+
"text": item.text,
|
|
36
|
+
"importance": float(item.importance),
|
|
37
|
+
"t": float(item.t),
|
|
38
|
+
"confidence": float(getattr(item, "confidence", 0.5)),
|
|
39
|
+
})
|
|
40
|
+
bond = m.social.bonds.get(str(user_id))
|
|
41
|
+
social = {
|
|
42
|
+
"trust": float(bond.trust) if bond else 0.5,
|
|
43
|
+
"attachment": float(bond.attachment) if bond else 0.1,
|
|
44
|
+
"familiarity": float(bond.familiarity) if bond else 0.1,
|
|
45
|
+
"responsiveness": float(bond.responsiveness) if bond else 0.5,
|
|
46
|
+
"stage": str(getattr(bond, "stage", "stranger")) if bond else "stranger",
|
|
47
|
+
} if bond else {}
|
|
48
|
+
episodic = []
|
|
49
|
+
for item in list(m.memory.episodic.items)[-200:]:
|
|
50
|
+
episodic.append({"text": item.text, "importance": float(item.importance), "t": float(item.t), "confidence": float(getattr(item, "confidence", 0.5))})
|
|
51
|
+
evolution = None
|
|
52
|
+
try:
|
|
53
|
+
for exp in getattr(session, "kernel", None).experiments:
|
|
54
|
+
if getattr(exp, "name", "") == "evolution" and hasattr(exp, "state"):
|
|
55
|
+
evolution = exp.state()
|
|
56
|
+
break
|
|
57
|
+
except Exception:
|
|
58
|
+
evolution = None
|
|
59
|
+
data = {
|
|
60
|
+
"user_id": str(user_id),
|
|
61
|
+
"evolution": evolution,
|
|
62
|
+
"social": social,
|
|
63
|
+
"episodic": episodic,
|
|
64
|
+
"t": float(m.t),
|
|
65
|
+
"identity": float(m.self_model.identity),
|
|
66
|
+
"drives": {k: float(v) for k, v in m.drives.values.items()},
|
|
67
|
+
"affect": {
|
|
68
|
+
"v": float(getattr(m.affect, "v", 0.0)),
|
|
69
|
+
"a": float(getattr(m.affect, "a", 0.0)),
|
|
70
|
+
"stress": float(getattr(m.affect, "stress", 0.0)),
|
|
71
|
+
},
|
|
72
|
+
"facts": facts,
|
|
73
|
+
"emotion": session.emotion.snapshot() if hasattr(session, "emotion") else None,
|
|
74
|
+
"turns": list(session.turns)[-30:],
|
|
75
|
+
}
|
|
76
|
+
self._path(user_id).write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
|
|
77
|
+
fly = getattr(getattr(session.mind, "circuit", None), "fly_mb", None)
|
|
78
|
+
if fly is not None:
|
|
79
|
+
np.savez_compressed(self._fly_path(user_id), w_approach=fly.w_approach, w_avoid=fly.w_avoid, trace=fly.trace,
|
|
80
|
+
sparsity=np.array([fly.sparsity]), lr=np.array([fly.lr]), apl_gain=np.array([fly.apl_gain]),
|
|
81
|
+
trace_decay=np.array([fly.trace_decay]), pam_lr=np.array([fly.pam_lr]), ppl1_lr=np.array([fly.ppl1_lr]),
|
|
82
|
+
reward_expectation=np.array([fly.reward_expectation]), punishment_expectation=np.array([fly.punishment_expectation]),
|
|
83
|
+
pam_gamma_ratio=np.array([fly.pam_gamma_ratio]), ppl1_alpha_ratio=np.array([fly.ppl1_alpha_ratio]),
|
|
84
|
+
weight_decay=np.array([fly.weight_decay]),
|
|
85
|
+
gamma=np.array([fly.subregion_gain["gamma"]]), alpha=np.array([fly.subregion_gain["alpha"]]), beta=np.array([fly.subregion_gain["beta"]]),
|
|
86
|
+
age_steps=np.array([fly.age_steps]), plasticity=np.array([fly.plasticity]))
|
|
87
|
+
except Exception:
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
def _load_fly(self, user_id: str, session) -> None:
|
|
91
|
+
fly_path = self._fly_path(user_id)
|
|
92
|
+
if not fly_path.exists():
|
|
93
|
+
return
|
|
94
|
+
try:
|
|
95
|
+
m = session.mind
|
|
96
|
+
fly = getattr(getattr(m, "circuit", None), "fly_mb", None)
|
|
97
|
+
if fly is None:
|
|
98
|
+
fly = FlyMushroomBody(seed=1)
|
|
99
|
+
m.circuit.fly_mb = fly
|
|
100
|
+
z = np.load(fly_path)
|
|
101
|
+
fly.w_approach = z["w_approach"]
|
|
102
|
+
fly.w_avoid = z["w_avoid"]
|
|
103
|
+
fly.trace = z["trace"]
|
|
104
|
+
fly.sparsity = float(z["sparsity"][0])
|
|
105
|
+
fly.lr = float(z["lr"][0])
|
|
106
|
+
fly.apl_gain = float(z["apl_gain"][0])
|
|
107
|
+
fly.trace_decay = float(z["trace_decay"][0])
|
|
108
|
+
fly.pam_lr = float(z["pam_lr"][0])
|
|
109
|
+
fly.ppl1_lr = float(z["ppl1_lr"][0])
|
|
110
|
+
fly.reward_expectation = float(z["reward_expectation"][0])
|
|
111
|
+
fly.punishment_expectation = float(z["punishment_expectation"][0])
|
|
112
|
+
fly.pam_gamma_ratio = float(z["pam_gamma_ratio"][0])
|
|
113
|
+
fly.ppl1_alpha_ratio = float(z["ppl1_alpha_ratio"][0])
|
|
114
|
+
fly.weight_decay = float(z["weight_decay"][0])
|
|
115
|
+
fly.subregion_gain = {"gamma": float(z["gamma"][0]), "alpha": float(z["alpha"][0]), "beta": float(z["beta"][0])}
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
def load(self, user_id: str, session) -> bool:
|
|
120
|
+
path = self._path(user_id)
|
|
121
|
+
if not path.exists():
|
|
122
|
+
return False
|
|
123
|
+
try:
|
|
124
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
125
|
+
m = session.mind
|
|
126
|
+
m.t = float(data.get("t", m.t))
|
|
127
|
+
m.self_model.identity = float(data.get("identity", m.self_model.identity))
|
|
128
|
+
for key, value in (data.get("drives") or {}).items():
|
|
129
|
+
if key in m.drives.values:
|
|
130
|
+
m.drives.values[key] = float(value)
|
|
131
|
+
aff = data.get("affect") or {}
|
|
132
|
+
m.affect.v = float(aff.get("v", m.affect.v))
|
|
133
|
+
m.affect.a = float(aff.get("a", m.affect.a))
|
|
134
|
+
m.affect.stress = float(aff.get("stress", m.affect.stress))
|
|
135
|
+
soc = data.get("social") or {}
|
|
136
|
+
if soc:
|
|
137
|
+
bond = m.social.bonds.setdefault(str(user_id), Bond())
|
|
138
|
+
bond.trust = float(soc.get("trust", bond.trust))
|
|
139
|
+
bond.attachment = float(soc.get("attachment", bond.attachment))
|
|
140
|
+
bond.familiarity = float(soc.get("familiarity", bond.familiarity))
|
|
141
|
+
bond.responsiveness = float(soc.get("responsiveness", bond.responsiveness))
|
|
142
|
+
bond.stage = str(soc.get("stage", bond.stage))
|
|
143
|
+
for item in data.get("episodic") or []:
|
|
144
|
+
m.memory.episodic.items.append(MemoryItem(
|
|
145
|
+
text=str(item.get("text") or ""),
|
|
146
|
+
importance=float(item.get("importance", 0.5)),
|
|
147
|
+
t=float(item.get("t", 0.0)),
|
|
148
|
+
confidence=float(item.get("confidence", 0.5)),
|
|
149
|
+
))
|
|
150
|
+
for item in data.get("facts") or []:
|
|
151
|
+
m.memory.semantic.items.append(MemoryItem(
|
|
152
|
+
text=str(item.get("text") or ""),
|
|
153
|
+
importance=float(item.get("importance", 0.5)),
|
|
154
|
+
t=float(item.get("t", 0.0)),
|
|
155
|
+
confidence=float(item.get("confidence", 0.5)),
|
|
156
|
+
))
|
|
157
|
+
session.turns = list(data.get("turns") or [])
|
|
158
|
+
try:
|
|
159
|
+
if hasattr(session, "emotion") and data.get("emotion"):
|
|
160
|
+
session.emotion.load(data.get("emotion"))
|
|
161
|
+
except Exception:
|
|
162
|
+
pass
|
|
163
|
+
evo = data.get("evolution")
|
|
164
|
+
if evo:
|
|
165
|
+
try:
|
|
166
|
+
for exp in getattr(session, "kernel", None).experiments:
|
|
167
|
+
if getattr(exp, "name", "") == "evolution" and hasattr(exp, "load_state"):
|
|
168
|
+
exp.load_state(evo)
|
|
169
|
+
break
|
|
170
|
+
except Exception:
|
|
171
|
+
pass
|
|
172
|
+
self._load_fly(user_id, session)
|
|
173
|
+
return True
|
|
174
|
+
except Exception:
|
|
175
|
+
return False
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Cold / active / warm session window, inspired by Animus heartbeat sessions."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SessionWindow:
|
|
6
|
+
def __init__(self, warmth_s: float = 900.0):
|
|
7
|
+
self.warmth_s = float(warmth_s)
|
|
8
|
+
self.state = "cold"
|
|
9
|
+
self.last_active = 0.0
|
|
10
|
+
self.warm_until = 0.0
|
|
11
|
+
|
|
12
|
+
def touch(self, now: float) -> bool:
|
|
13
|
+
was_warm = self.state == "warm" and float(now) <= self.warm_until
|
|
14
|
+
self.state = "active"
|
|
15
|
+
self.last_active = float(now)
|
|
16
|
+
return bool(was_warm)
|
|
17
|
+
|
|
18
|
+
def finish(self, now: float) -> None:
|
|
19
|
+
self.state = "warm"
|
|
20
|
+
self.warm_until = float(now) + self.warmth_s
|
|
21
|
+
|
|
22
|
+
def tick(self, now: float) -> None:
|
|
23
|
+
if self.state == "warm" and float(now) > self.warm_until:
|
|
24
|
+
self.state = "cold"
|
|
25
|
+
|
|
26
|
+
def hint(self, now: float) -> str:
|
|
27
|
+
if self.state == "cold":
|
|
28
|
+
return ""
|
|
29
|
+
if self.state == "warm" and float(now) <= self.warm_until:
|
|
30
|
+
return "处于连续对话中,接着刚才说;不要重新自我介绍,不要重复问已经知道的信息。"
|
|
31
|
+
return ""
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List
|
|
4
|
+
|
|
5
|
+
from . import MindBase, MindConfig
|
|
6
|
+
from .persona import Persona
|
|
7
|
+
from .relationship_session import RelationshipSession
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def replay_numeric(texts: List[str], profile: Dict[str, float], seed: int = 1) -> Dict[str, float]:
|
|
11
|
+
mb = MindBase(MindConfig(seed=seed))
|
|
12
|
+
try:
|
|
13
|
+
Persona.from_profile(profile, seed=seed).apply_to(mb)
|
|
14
|
+
except Exception:
|
|
15
|
+
pass
|
|
16
|
+
s = RelationshipSession(mb)
|
|
17
|
+
for text in texts:
|
|
18
|
+
s.say(str(text), 0.2)
|
|
19
|
+
return {
|
|
20
|
+
"trust": round(float(mb.social.trust_for(s.user)), 4),
|
|
21
|
+
"identity": round(float(mb.self_model.identity), 4),
|
|
22
|
+
"social": round(float(mb.drives.values.get("social", 0.0)), 4),
|
|
23
|
+
"stress": round(float(mb.affect.stress), 4),
|
|
24
|
+
"memory": len(mb.memory.semantic.items) + len(mb.memory.episodic.items),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def shadow_compare(texts: List[str], base: Dict[str, float], delta: Dict[str, float], seed: int = 1) -> Dict[str, object]:
|
|
29
|
+
from .character_profile import merge
|
|
30
|
+
a = replay_numeric(texts, base, seed=seed)
|
|
31
|
+
b = replay_numeric(texts, merge(base, delta), seed=seed)
|
|
32
|
+
return {
|
|
33
|
+
"n": len(texts),
|
|
34
|
+
"base": a,
|
|
35
|
+
"candidate": b,
|
|
36
|
+
"diff": {k: round(float(b[k]) - float(a[k]), 4) for k in a},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def audit_user(base, delta, episodes, n: int = 10) -> dict:
|
|
41
|
+
texts = [str(x.get("text") or "") for x in episodes[-n:] if x.get("text")]
|
|
42
|
+
return shadow_compare(texts, base, delta) if texts else {"n": 0}
|