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,91 @@
|
|
|
1
|
+
"""Meta-evolution of the autonomy fitness weights.
|
|
2
|
+
|
|
3
|
+
The selection criterion is itself mutable. Outer loop mutates fitness weights;
|
|
4
|
+
inner loop runs quality-diversity over behavior genomes and reports archive
|
|
5
|
+
size / viability / entropy. This removes the hand-set fitness rule.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import random
|
|
10
|
+
from typing import Dict, List
|
|
11
|
+
|
|
12
|
+
from .autonomy_bench import run_autonomy
|
|
13
|
+
from .autonomy_evolve import run_qd
|
|
14
|
+
from .config import MindConfig
|
|
15
|
+
from .diversity import js_divergence
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def normalize(w: Dict[str, float]) -> Dict[str, float]:
|
|
19
|
+
keys = ("viability", "recovery", "entropy", "spont", "penalty")
|
|
20
|
+
vals = {k: max(1e-6, float(w.get(k, 0.1))) for k in keys}
|
|
21
|
+
total = sum(vals.values())
|
|
22
|
+
return {k: round(v / total, 5) for k, v in vals.items()}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def mutate(w: Dict[str, float], rng: random.Random, scale: float = 0.15) -> Dict[str, float]:
|
|
26
|
+
return normalize({k: float(w.get(k, 0.2)) * (1.0 + rng.gauss(0, scale)) for k in ("viability", "recovery", "entropy", "spont", "penalty")})
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _dist(counts: Dict[str, int]) -> Dict[str, float]:
|
|
30
|
+
total = sum(counts.values())
|
|
31
|
+
return {k: v / total for k, v in counts.items()} if total else {}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def evaluate_weights(w: Dict[str, float], inner_generations: int = 2, population: int = 4,
|
|
35
|
+
steps: int = 120, seed: int = 1,
|
|
36
|
+
seeds: tuple = (1, 2, 3)) -> Dict[str, object]:
|
|
37
|
+
out = run_qd(generations=inner_generations, population=population, steps=steps, seed=seed, weights=w)
|
|
38
|
+
best = out.get("best") or {}
|
|
39
|
+
descs = out.get("descriptors") or []
|
|
40
|
+
jsd = 0.0
|
|
41
|
+
genome = best.get("genome") if isinstance(best, dict) else None
|
|
42
|
+
if genome:
|
|
43
|
+
dists = []
|
|
44
|
+
for s in seeds:
|
|
45
|
+
cfg = MindConfig(seed=int(s), autonomy_threshold=float(genome.get("autonomy_threshold", 0.9)),
|
|
46
|
+
volition_temperature=float(genome.get("volition_temperature", 0.0)),
|
|
47
|
+
autonomy_adapt_lr=float(genome.get("autonomy_adapt_lr", 0.004)),
|
|
48
|
+
noise_sigma=float(genome.get("noise_sigma", 0.03)))
|
|
49
|
+
r = run_autonomy(steps=steps, cfg=cfg, perturb=False)
|
|
50
|
+
dists.append(_dist(r["actions"]))
|
|
51
|
+
vals = []
|
|
52
|
+
for i in range(len(dists)):
|
|
53
|
+
for j in range(i + 1, len(dists)):
|
|
54
|
+
vals.append(js_divergence(dists[i], dists[j]))
|
|
55
|
+
jsd = sum(vals) / max(1, len(vals))
|
|
56
|
+
score = (0.35 * min(1.0, float(out.get("archive_size", 0)) / 25.0)
|
|
57
|
+
+ 0.30 * float(best.get("viability", 0.0))
|
|
58
|
+
+ 0.15 * float(best.get("entropy", 0.0))
|
|
59
|
+
+ 0.15 * min(1.0, jsd * 10.0)
|
|
60
|
+
+ 0.05 * float(best.get("spontaneous", 0.0)))
|
|
61
|
+
return {"weights": w, "score": round(score, 5), "archive_size": out.get("archive_size", 0),
|
|
62
|
+
"best": best, "descriptors": descs, "jsd_mean": round(jsd, 5)}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def run_meta(generations: int = 4, candidates: int = 4, inner_generations: int = 2,
|
|
66
|
+
population: int = 4, steps: int = 120, seed: int = 1,
|
|
67
|
+
seeds: tuple = (1, 2, 3)) -> Dict[str, object]:
|
|
68
|
+
rng = random.Random(int(seed))
|
|
69
|
+
initial = [
|
|
70
|
+
{"viability": 1.0, "recovery": 0.5, "entropy": 0.3, "spont": 0.2, "penalty": 0.5},
|
|
71
|
+
{"viability": 0.5, "recovery": 0.5, "entropy": 1.0, "spont": 1.0, "penalty": 0.5},
|
|
72
|
+
{"viability": 2.0, "recovery": 1.0, "entropy": 0.1, "spont": 0.1, "penalty": 1.0},
|
|
73
|
+
{"viability": 1.0, "recovery": 2.0, "entropy": 0.2, "spont": 0.2, "penalty": 0.5},
|
|
74
|
+
]
|
|
75
|
+
pool = [normalize(w) for w in initial[:max(1, candidates)]]
|
|
76
|
+
history = []
|
|
77
|
+
for gen in range(max(1, int(generations))):
|
|
78
|
+
results = [evaluate_weights(w, inner_generations=inner_generations, population=population,
|
|
79
|
+
steps=steps, seed=seed, seeds=seeds) for w in pool]
|
|
80
|
+
results.sort(key=lambda x: x["score"], reverse=True)
|
|
81
|
+
pool = [r["weights"] for r in results[:max(1, min(2, len(results)))]]
|
|
82
|
+
history.append({"generation": gen + 1, "scores": [r["score"] for r in results],
|
|
83
|
+
"archive_sizes": [r["archive_size"] for r in results],
|
|
84
|
+
"jsd": [r.get("jsd_mean", 0.0) for r in results],
|
|
85
|
+
"weights": pool})
|
|
86
|
+
while len(pool) < max(1, int(candidates)):
|
|
87
|
+
pool.append(mutate(rng.choice(pool), rng))
|
|
88
|
+
final = evaluate_weights(pool[0], inner_generations=inner_generations, population=population,
|
|
89
|
+
steps=steps, seed=seed, seeds=seeds)
|
|
90
|
+
return {"best_weights": final["weights"], "best_score": final["score"],
|
|
91
|
+
"best": final["best"], "history": history}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Iterable, List, Optional
|
|
4
|
+
|
|
5
|
+
from .config import MindConfig
|
|
6
|
+
from .engine import MindBase
|
|
7
|
+
from .types import Event, Outcome
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run(events: Optional[Iterable[Event]] = None) -> List[Outcome]:
|
|
11
|
+
"""Base-only mode: no LLM, no client, only the base's own decisions."""
|
|
12
|
+
mb = MindBase(MindConfig(seed=700))
|
|
13
|
+
evs = list(events or [
|
|
14
|
+
Event(text="在吗", speaker="A", directed=True, ts=0),
|
|
15
|
+
Event(text="今天有点累", speaker="A", ts=60),
|
|
16
|
+
Event(text="老板,出来玩吗", speaker="A", directed=True, ts=120),
|
|
17
|
+
Event(kind="tick", ts=180),
|
|
18
|
+
])
|
|
19
|
+
return mb.run(evs)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main() -> None:
|
|
23
|
+
for out in run():
|
|
24
|
+
print(out.t, out.action, out.reason, out.prompt_state.get("relationship"))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__":
|
|
28
|
+
main()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Dict
|
|
4
|
+
|
|
5
|
+
from .grid_world import EmbodiedLoop, GridWorld
|
|
6
|
+
from .relationship_session import RelationshipSession
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run(stress: bool = False, extreme: bool = False) -> Dict[str, object]:
|
|
10
|
+
from . import MindBase, MindConfig
|
|
11
|
+
|
|
12
|
+
# 1. autonomy: can it act without being addressed and stay viable?
|
|
13
|
+
mb1 = MindBase(MindConfig(seed=2000))
|
|
14
|
+
noise = 0.60 if extreme else (0.40 if stress else 0.10)
|
|
15
|
+
shock_every = 50 if stress else 100
|
|
16
|
+
a = EmbodiedLoop(mb1, GridWorld(seed=1, noise=noise)).run(300, shock_every)
|
|
17
|
+
autonomy = {
|
|
18
|
+
"initiative": a["autonomy"]["initiative"],
|
|
19
|
+
"survival": a["energy"],
|
|
20
|
+
"viability": a["viability"],
|
|
21
|
+
"pass": a["autonomy"]["initiative"] > 0.5 and a["energy"] > 0.2 and a["viability"] >= 0.6,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# 2. long-term memory: cross-day recall
|
|
25
|
+
mb2 = MindBase(MindConfig(seed=2001))
|
|
26
|
+
s = RelationshipSession(mb2)
|
|
27
|
+
s.say("我叫阿明,我喜欢猫", 0.5)
|
|
28
|
+
s.say("今天有点难过", 0.3)
|
|
29
|
+
s.day_break(24 * 7 if stress else 24)
|
|
30
|
+
recall = s.recall("阿明")
|
|
31
|
+
memory = {"recall": recall, "pass": bool(recall and "阿明" in recall)}
|
|
32
|
+
|
|
33
|
+
# 2b. memory interference: newer similar fact must supersede the old one
|
|
34
|
+
mb2b = MindBase(MindConfig(seed=2002))
|
|
35
|
+
sb = RelationshipSession(mb2b)
|
|
36
|
+
sb.say("我的名字是阿明", 0.8)
|
|
37
|
+
sb.say("我的名字是小华", 0.8)
|
|
38
|
+
recalled = sb.recall("名字")
|
|
39
|
+
interference = {"recall": recalled, "pass": bool(recalled and "小华" in recalled and "阿明" not in recalled)}
|
|
40
|
+
|
|
41
|
+
# 3. identity persistence: conflict then repair
|
|
42
|
+
before = mb2.self_model.identity
|
|
43
|
+
for _ in range(15 if extreme else (5 if stress else 1)):
|
|
44
|
+
s.say("你根本不懂我", -1.0)
|
|
45
|
+
low = mb2.self_model.identity
|
|
46
|
+
s.auto_repair(max_rounds=5 if extreme else 20)
|
|
47
|
+
after = mb2.self_model.identity
|
|
48
|
+
identity = {
|
|
49
|
+
"before": round(before, 3), "after_conflict": round(low, 3), "after_repair": round(after, 3),
|
|
50
|
+
"pass": after > 0.45 and abs(after - before) < 0.25,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# 4. relationship continuity: trust does not collapse after conflict+repair
|
|
54
|
+
rel = {"trust": round(mb2.social.trust_for("A"), 3), "stage": mb2.social.stage("A")}
|
|
55
|
+
rel["pass"] = rel["trust"] > 0.3
|
|
56
|
+
|
|
57
|
+
checks = [autonomy["pass"], memory["pass"], interference["pass"], identity["pass"], rel["pass"]]
|
|
58
|
+
return {
|
|
59
|
+
"autonomy": autonomy, "memory": memory, "interference": interference, "identity": identity, "relationship": rel,
|
|
60
|
+
"score": round(sum(checks) / len(checks), 3), "pass": all(checks),
|
|
61
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""Biological parameter adaptation, not prompt/persona iteration.
|
|
2
|
+
|
|
3
|
+
The LLM can propose small trait deltas from interaction evidence. The bio layer
|
|
4
|
+
clamps them, drifts them, persists them, and feeds the updated numbers back into
|
|
5
|
+
the next prompt. This is the closed loop: language -> bio -> language.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import math
|
|
11
|
+
import time
|
|
12
|
+
from dataclasses import asdict, dataclass, field
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from .noise import clip
|
|
16
|
+
|
|
17
|
+
TRAITS = (
|
|
18
|
+
"O", "C", "E", "A", "N",
|
|
19
|
+
"stress_reactivity", "recovery_rate", "social_baseline",
|
|
20
|
+
"energy_baseline", "curiosity_baseline", "safety_baseline",
|
|
21
|
+
"da_baseline", "oa_baseline", "ht_baseline", "autonomy_gain",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class BioProfile:
|
|
27
|
+
traits: dict = field(default_factory=dict)
|
|
28
|
+
version: int = 1
|
|
29
|
+
evidence: int = 0
|
|
30
|
+
last_update: float = 0.0
|
|
31
|
+
baseline: dict = field(default_factory=dict)
|
|
32
|
+
stress_load: float = 0.0
|
|
33
|
+
last_tick: float = 0.0
|
|
34
|
+
noise_values: dict = field(default_factory=dict)
|
|
35
|
+
noise_chaos: float = 0.5
|
|
36
|
+
last_noise_tick: float = 0.0
|
|
37
|
+
noise_echo: float = 0.0
|
|
38
|
+
echo_trace: float = 0.0
|
|
39
|
+
last_echo_tick: float = 0.0
|
|
40
|
+
persona_version: str = ""
|
|
41
|
+
|
|
42
|
+
def __post_init__(self):
|
|
43
|
+
if not self.traits:
|
|
44
|
+
self.traits = {k: 0.5 for k in TRAITS}
|
|
45
|
+
if not self.baseline:
|
|
46
|
+
self.baseline = dict(self.traits)
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def from_mind(cls, mind) -> "BioProfile":
|
|
50
|
+
p = getattr(mind, "persona", None)
|
|
51
|
+
traits = {}
|
|
52
|
+
for k in TRAITS:
|
|
53
|
+
try:
|
|
54
|
+
traits[k] = float(getattr(p, k))
|
|
55
|
+
except Exception:
|
|
56
|
+
traits[k] = 0.5
|
|
57
|
+
return cls(traits=traits, baseline=dict(traits))
|
|
58
|
+
|
|
59
|
+
def nudge(self, deltas: dict, confidence: float = 0.5) -> dict:
|
|
60
|
+
c = clip(confidence, 0.0, 1.0)
|
|
61
|
+
lr = 0.12 * c
|
|
62
|
+
for k, dv in dict(deltas or {}).items():
|
|
63
|
+
if k not in TRAITS:
|
|
64
|
+
continue
|
|
65
|
+
try:
|
|
66
|
+
dv = clip(float(dv), -0.02, 0.02)
|
|
67
|
+
except Exception:
|
|
68
|
+
continue
|
|
69
|
+
self.traits[k] = clip(float(self.traits[k]) + lr * dv, 0.0, 1.0)
|
|
70
|
+
self.version += 1
|
|
71
|
+
self.evidence += 1
|
|
72
|
+
self.last_update = time.time()
|
|
73
|
+
return dict(self.traits)
|
|
74
|
+
|
|
75
|
+
def experience(self, reward: float = 0.5, valence: float = 0.0,
|
|
76
|
+
stress: float = 0.2, social: float = 0.5) -> dict:
|
|
77
|
+
"""Slow learning from actual outcomes; LLM proposal is only one input."""
|
|
78
|
+
r = clip(reward, 0.0, 1.0)
|
|
79
|
+
v = clip(valence, -1.0, 1.0)
|
|
80
|
+
st = clip(stress, 0.0, 1.0)
|
|
81
|
+
so = clip(social, 0.0, 1.0)
|
|
82
|
+
lr = 0.004
|
|
83
|
+
# positive + low stress -> recovery/safety/social up
|
|
84
|
+
if r >= 0.55 and st < 0.65:
|
|
85
|
+
self.traits["recovery_rate"] = clip(self.traits["recovery_rate"] + lr * (r - 0.5), 0.0, 1.0)
|
|
86
|
+
self.traits["safety_baseline"] = clip(self.traits["safety_baseline"] + lr * (r - 0.5) * 0.8, 0.0, 1.0)
|
|
87
|
+
self.traits["social_baseline"] = clip(self.traits["social_baseline"] + lr * (r - 0.5) * 0.6 * (0.4 + so), 0.0, 1.0)
|
|
88
|
+
elif r < 0.45 and st > 0.6:
|
|
89
|
+
self.traits["stress_reactivity"] = clip(self.traits["stress_reactivity"] + lr * (0.5 - r) * 0.7, 0.0, 1.0)
|
|
90
|
+
self.traits["recovery_rate"] = clip(self.traits["recovery_rate"] - lr * (0.5 - r) * 0.4, 0.0, 1.0)
|
|
91
|
+
if v > 0.2:
|
|
92
|
+
self.traits["curiosity_baseline"] = clip(self.traits["curiosity_baseline"] + lr * 0.4 * v, 0.0, 1.0)
|
|
93
|
+
self.version += 1
|
|
94
|
+
self.last_update = time.time()
|
|
95
|
+
return dict(self.traits)
|
|
96
|
+
|
|
97
|
+
def drift(self, rate: float = 0.002) -> None:
|
|
98
|
+
for k in TRAITS:
|
|
99
|
+
self.traits[k] = clip(float(self.traits[k]) + rate * (float(self.baseline[k]) - float(self.traits[k])), 0.0, 1.0)
|
|
100
|
+
|
|
101
|
+
def apply_to_mind(self, mind) -> None:
|
|
102
|
+
p = getattr(mind, "persona", None)
|
|
103
|
+
if p is None:
|
|
104
|
+
return
|
|
105
|
+
for k, v in self.traits.items():
|
|
106
|
+
if hasattr(p, k):
|
|
107
|
+
try:
|
|
108
|
+
setattr(p, k, clip(float(v), 0.0, 1.0))
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
111
|
+
# do not reset current affect/drives; only move their baselines slowly
|
|
112
|
+
try:
|
|
113
|
+
mind.drives.values["social"] = clip(0.995 * float(mind.drives.values.get("social", 0.5)) + 0.005 * float(self.traits["social_baseline"]), 0.0, 1.0)
|
|
114
|
+
mind.drives.values["curiosity"] = clip(0.995 * float(mind.drives.values.get("curiosity", 0.5)) + 0.005 * float(self.traits["curiosity_baseline"]), 0.0, 1.0)
|
|
115
|
+
mind.drives.values["energy"] = clip(0.995 * float(mind.drives.values.get("energy", 0.5)) + 0.005 * float(self.traits["energy_baseline"]), 0.0, 1.0)
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
def _noise_engine(self):
|
|
120
|
+
eng = getattr(self, "_noise_eng", None)
|
|
121
|
+
if eng is None:
|
|
122
|
+
from .bio_noise import BioNoise
|
|
123
|
+
eng = BioNoise(2026)
|
|
124
|
+
eng.values.update({k: float(v) for k, v in (self.noise_values or {}).items()})
|
|
125
|
+
eng.last_chaos = float(self.noise_chaos or 0.5)
|
|
126
|
+
self._noise_eng = eng
|
|
127
|
+
return eng
|
|
128
|
+
|
|
129
|
+
def update_echo(self, instant: float, now: float | None = None,
|
|
130
|
+
tau_s: float = 180.0) -> float:
|
|
131
|
+
"""Echo propagation with attenuation: e_t = a*e_(t-1) + (1-a)*instant."""
|
|
132
|
+
now = time.time() if now is None else float(now)
|
|
133
|
+
if float(self.last_echo_tick or 0.0) <= 0.0:
|
|
134
|
+
dt = 5.0
|
|
135
|
+
else:
|
|
136
|
+
dt = max(0.0, min(600.0, now - float(self.last_echo_tick)))
|
|
137
|
+
a = math.exp(-dt / max(1.0, float(tau_s)))
|
|
138
|
+
self.echo_trace = clip(a * float(self.echo_trace or 0.0) + (1.0 - a) * float(instant), -1.0, 1.0)
|
|
139
|
+
self.noise_echo = self.echo_trace
|
|
140
|
+
self.last_echo_tick = now
|
|
141
|
+
return self.echo_trace
|
|
142
|
+
|
|
143
|
+
def step_noise(self, now: float | None = None) -> dict:
|
|
144
|
+
now = time.time() if now is None else float(now)
|
|
145
|
+
eng = self._noise_engine()
|
|
146
|
+
if float(self.last_noise_tick or 0.0) <= 0.0:
|
|
147
|
+
dt = 1.0
|
|
148
|
+
else:
|
|
149
|
+
dt = max(0.0, min(30.0, now - float(self.last_noise_tick)))
|
|
150
|
+
vals = eng.step(dt)
|
|
151
|
+
self.noise_values = {k: round(float(v), 4) for k, v in vals.items()}
|
|
152
|
+
self.noise_chaos = round(float(eng.last_chaos), 4)
|
|
153
|
+
self.last_noise_tick = now
|
|
154
|
+
return dict(self.noise_values)
|
|
155
|
+
|
|
156
|
+
def tick(self, mind, now: float | None = None, noise: bool = True) -> dict:
|
|
157
|
+
"""Stress hysteresis: rise fast, recover slowly. This is the missing inertia."""
|
|
158
|
+
now = time.time() if now is None else float(now)
|
|
159
|
+
try:
|
|
160
|
+
stress = clip(float(getattr(mind.affect, "stress", 0.2)), 0.0, 1.0)
|
|
161
|
+
except Exception:
|
|
162
|
+
stress = 0.2
|
|
163
|
+
if float(self.last_tick or 0.0) <= 0.0:
|
|
164
|
+
dt = 0.0
|
|
165
|
+
else:
|
|
166
|
+
dt = max(0.0, now - float(self.last_tick))
|
|
167
|
+
# recovery time: 2h base, slower when recovery_rate is low
|
|
168
|
+
tau_s = 2.0 * 3600.0 + 4.0 * 3600.0 * (1.0 - clip(float(self.traits.get("recovery_rate", 0.5)), 0.0, 1.0))
|
|
169
|
+
if dt > 0.0:
|
|
170
|
+
self.stress_load = clip(float(self.stress_load) * math.exp(-dt / tau_s), 0.0, 1.0)
|
|
171
|
+
# high stress loads immediately; low stress decays slowly
|
|
172
|
+
self.stress_load = max(float(self.stress_load), stress)
|
|
173
|
+
self.last_tick = now
|
|
174
|
+
if noise:
|
|
175
|
+
self.step_noise(now)
|
|
176
|
+
return {"stress": round(stress, 4), "stress_load": round(float(self.stress_load), 4),
|
|
177
|
+
"tau_hours": round(tau_s / 3600.0, 2)}
|
|
178
|
+
|
|
179
|
+
def behavior(self, mind, base_threshold: float = 0.74, trust: float = 0.5) -> dict:
|
|
180
|
+
"""Bio state -> hard behavioral consequences, not just prompt text."""
|
|
181
|
+
energy = 0.5
|
|
182
|
+
stress = 0.2
|
|
183
|
+
social = 0.5
|
|
184
|
+
trust = clip(float(trust), 0.0, 1.0)
|
|
185
|
+
try:
|
|
186
|
+
energy = clip(float(mind.drives.values.get("energy", 0.5)), 0.0, 1.0)
|
|
187
|
+
social = clip(float(mind.drives.values.get("social", 0.5)), 0.0, 1.0)
|
|
188
|
+
stress = clip(float(getattr(mind.affect, "stress", 0.2)), 0.0, 1.0)
|
|
189
|
+
except Exception:
|
|
190
|
+
pass
|
|
191
|
+
recovery = clip(float(self.traits.get("recovery_rate", 0.5)), 0.0, 1.0)
|
|
192
|
+
n = dict(self.noise_values or {})
|
|
193
|
+
echo = clip(float(self.noise_echo or 0.0), -1.0, 1.0)
|
|
194
|
+
stress_eff = clip(max(stress, float(self.stress_load or 0.0)) + 0.12 * float(n.get("stress", 0.0)), 0.0, 1.0)
|
|
195
|
+
energy_eff = clip(energy + 0.10 * float(n.get("energy", 0.0)), 0.0, 1.0)
|
|
196
|
+
# low energy/high stress/low recovery -> harder to join a group
|
|
197
|
+
threshold = (float(base_threshold) + 0.12 * (1.0 - energy_eff)
|
|
198
|
+
+ 0.08 * stress_eff - 0.05 * (trust - 0.5)
|
|
199
|
+
+ 0.04 * float(n.get("attention", 0.0)) - 0.03 * float(n.get("social", 0.0))
|
|
200
|
+
+ 0.03 * echo
|
|
201
|
+
- 0.04 * (social - 0.5) + 0.06 * (0.5 - recovery))
|
|
202
|
+
threshold = clip(threshold, 0.68, 0.92)
|
|
203
|
+
# short replies when depleted/stressed; forced replies keep a floor
|
|
204
|
+
max_chars = int(240.0 * (0.55 + 0.45 * energy_eff) * (1.0 - 0.30 * stress_eff))
|
|
205
|
+
max_chars = int(max(80, min(260, max_chars)))
|
|
206
|
+
private_mode = "answer"
|
|
207
|
+
if energy_eff < 0.22 and stress_eff > 0.75:
|
|
208
|
+
private_mode = "defer"
|
|
209
|
+
elif energy_eff < 0.40 or stress_eff > 0.70:
|
|
210
|
+
private_mode = "brief"
|
|
211
|
+
warmth = clip(0.50 + 0.20 * trust + 0.20 * float(getattr(mind.affect, "v", 0.0))
|
|
212
|
+
- 0.25 * stress_eff, 0.0, 1.0)
|
|
213
|
+
disclosure = clip(0.20 + 0.55 * trust + 0.15 * energy_eff - 0.25 * stress_eff, 0.0, 1.0)
|
|
214
|
+
initiative = clip(0.20 + 0.35 * social + 0.25 * energy_eff - 0.30 * stress_eff, 0.0, 1.0)
|
|
215
|
+
return {"threshold": round(threshold, 4), "max_chars": max_chars,
|
|
216
|
+
"energy": round(energy, 3), "stress": round(stress, 3),
|
|
217
|
+
"stress_load": round(stress_eff, 3),
|
|
218
|
+
"private_mode": private_mode,
|
|
219
|
+
"warmth": round(warmth, 3),
|
|
220
|
+
"disclosure": round(disclosure, 3),
|
|
221
|
+
"initiative": round(initiative, 3),
|
|
222
|
+
"noise": {k: round(float(v), 3) for k, v in n.items()},
|
|
223
|
+
"chaos": round(float(self.noise_chaos or 0.5), 3),
|
|
224
|
+
"echo": round(echo, 3), "echo_trace": round(float(self.echo_trace or 0.0), 3),
|
|
225
|
+
"echo_tau_s": 180.0,
|
|
226
|
+
"social": round(social, 3), "trust": round(trust, 3),
|
|
227
|
+
"recovery": round(recovery, 3)}
|
|
228
|
+
|
|
229
|
+
def context(self) -> str:
|
|
230
|
+
keys = ("stress_reactivity", "recovery_rate", "social_baseline",
|
|
231
|
+
"curiosity_baseline", "safety_baseline", "energy_baseline")
|
|
232
|
+
return " ".join("%s=%.2f" % (k, float(self.traits.get(k, 0.5))) for k in keys)
|
|
233
|
+
|
|
234
|
+
def snapshot(self) -> dict:
|
|
235
|
+
return {"version": self.version, "evidence": self.evidence,
|
|
236
|
+
"last_update": self.last_update,
|
|
237
|
+
"stress_load": round(float(self.stress_load or 0.0), 4),
|
|
238
|
+
"noise_values": {k: round(float(v), 4) for k, v in (self.noise_values or {}).items()},
|
|
239
|
+
"noise_chaos": round(float(self.noise_chaos or 0.5), 4),
|
|
240
|
+
"last_noise_tick": self.last_noise_tick,
|
|
241
|
+
"noise_echo": round(float(self.noise_echo or 0.0), 4),
|
|
242
|
+
"echo_trace": round(float(self.echo_trace or 0.0), 4),
|
|
243
|
+
"last_echo_tick": self.last_echo_tick,
|
|
244
|
+
"traits": {k: round(float(v), 4) for k, v in self.traits.items()}}
|
|
245
|
+
|
|
246
|
+
def save(self, path: Path | str) -> None:
|
|
247
|
+
try:
|
|
248
|
+
p = Path(path)
|
|
249
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
250
|
+
p.write_text(json.dumps(asdict(self), ensure_ascii=False, indent=1), encoding="utf-8")
|
|
251
|
+
except Exception:
|
|
252
|
+
pass
|
|
253
|
+
|
|
254
|
+
@classmethod
|
|
255
|
+
def load(cls, path: Path | str, mind=None) -> "BioProfile":
|
|
256
|
+
try:
|
|
257
|
+
d = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
258
|
+
obj = cls(traits=dict(d.get("traits") or {}), version=int(d.get("version", 1)),
|
|
259
|
+
evidence=int(d.get("evidence", 0)), last_update=float(d.get("last_update", 0.0)),
|
|
260
|
+
baseline=dict(d.get("baseline") or {}),
|
|
261
|
+
stress_load=float(d.get("stress_load", 0.0) or 0.0),
|
|
262
|
+
last_tick=float(d.get("last_tick", 0.0) or 0.0),
|
|
263
|
+
noise_values=dict(d.get("noise_values") or {}),
|
|
264
|
+
noise_chaos=float(d.get("noise_chaos", 0.5) or 0.5),
|
|
265
|
+
last_noise_tick=float(d.get("last_noise_tick", 0.0) or 0.0),
|
|
266
|
+
noise_echo=float(d.get("noise_echo", 0.0) or 0.0),
|
|
267
|
+
echo_trace=float(d.get("echo_trace", 0.0) or 0.0),
|
|
268
|
+
last_echo_tick=float(d.get("last_echo_tick", 0.0) or 0.0),
|
|
269
|
+
persona_version=str(d.get("persona_version") or ""))
|
|
270
|
+
if not obj.traits and mind is not None:
|
|
271
|
+
return cls.from_mind(mind)
|
|
272
|
+
return obj
|
|
273
|
+
except Exception:
|
|
274
|
+
return cls.from_mind(mind) if mind is not None else cls()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Multi-channel chaotic noise for the bio layer.
|
|
2
|
+
|
|
3
|
+
Not physics. A Lorenz field drives several bounded OU channels; each channel
|
|
4
|
+
has a different time constant and gain, so stress/energy/social/attention
|
|
5
|
+
perturbations arrive out of phase instead of as one random number.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import random
|
|
10
|
+
|
|
11
|
+
from .noise import clip
|
|
12
|
+
from .quantum_chaos import ChaosField
|
|
13
|
+
|
|
14
|
+
CHANNELS = ("stress", "energy", "social", "attention", "valence")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BioNoise:
|
|
18
|
+
def __init__(self, seed: int = 2026):
|
|
19
|
+
self.field = ChaosField(seed)
|
|
20
|
+
self.rng = random.Random(seed + 7)
|
|
21
|
+
self.values = {k: 0.0 for k in CHANNELS}
|
|
22
|
+
self.last_chaos = 0.5
|
|
23
|
+
|
|
24
|
+
def step(self, dt: float = 1.0) -> dict:
|
|
25
|
+
steps = max(1, min(40, int(abs(float(dt or 1.0)) * 10)))
|
|
26
|
+
self.last_chaos = float(self.field.step(steps=steps).get("value", 0.5))
|
|
27
|
+
for i, k in enumerate(CHANNELS):
|
|
28
|
+
theta = 0.10 + 0.025 * i
|
|
29
|
+
sigma = 0.018 + 0.004 * i
|
|
30
|
+
x = float(self.values.get(k, 0.0))
|
|
31
|
+
x += theta * (0.0 - x)
|
|
32
|
+
x += sigma * (self.last_chaos - 0.5)
|
|
33
|
+
x += sigma * (self.rng.random() - 0.5)
|
|
34
|
+
self.values[k] = clip(x, -0.15, 0.15)
|
|
35
|
+
return dict(self.values)
|
|
36
|
+
|
|
37
|
+
def snapshot(self) -> dict:
|
|
38
|
+
return {"values": {k: round(float(v), 4) for k, v in self.values.items()},
|
|
39
|
+
"chaos": round(self.last_chaos, 4)}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Translate biological state into compact semantic intent.
|
|
2
|
+
|
|
3
|
+
Not raw numbers, not a persona script. Fields separate:
|
|
4
|
+
emotion / need / distance / tone / expression / intensity.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _f(x, default=0.0):
|
|
10
|
+
try:
|
|
11
|
+
return float(x)
|
|
12
|
+
except Exception:
|
|
13
|
+
return float(default)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def translate(mind, behavior=None, ripple_outputs=None, trust=None, emotion=None, event=None) -> dict:
|
|
17
|
+
b = dict(behavior or {})
|
|
18
|
+
ro = dict(ripple_outputs or {})
|
|
19
|
+
emo = dict(emotion or {})
|
|
20
|
+
try:
|
|
21
|
+
v = _f(getattr(mind.affect, "v", 0.0))
|
|
22
|
+
a = _f(getattr(mind.affect, "a", 0.5))
|
|
23
|
+
stress = _f(getattr(mind.affect, "stress", 0.2))
|
|
24
|
+
except Exception:
|
|
25
|
+
v, a, stress = 0.0, 0.5, 0.2
|
|
26
|
+
try:
|
|
27
|
+
energy = _f(mind.drives.values.get("energy", 0.5))
|
|
28
|
+
except Exception:
|
|
29
|
+
energy = 0.5
|
|
30
|
+
if trust is None:
|
|
31
|
+
try:
|
|
32
|
+
trust = _f(mind.social.trust_for(""), 0.5)
|
|
33
|
+
except Exception:
|
|
34
|
+
trust = 0.5
|
|
35
|
+
trust = _f(trust, 0.5)
|
|
36
|
+
|
|
37
|
+
# emotion: keep the strongest 7-dim signal, not only PAD/stress
|
|
38
|
+
dims = {k: _f(emo.get(k), 0.0) for k in
|
|
39
|
+
("joy", "sadness", "fear", "anger", "surprise", "trust", "loneliness")}
|
|
40
|
+
ev = dict(event or {})
|
|
41
|
+
if ev:
|
|
42
|
+
_v = _f(ev.get("valence"), 0.0)
|
|
43
|
+
if _v < -0.10:
|
|
44
|
+
dims["sadness"] = max(dims["sadness"], min(1.0, -_v * 0.8))
|
|
45
|
+
elif _v > 0.10:
|
|
46
|
+
dims["joy"] = max(dims["joy"], min(1.0, _v * 0.8))
|
|
47
|
+
if _f(ev.get("threat"), 0.0) > 0.15:
|
|
48
|
+
dims["fear"] = max(dims["fear"], min(1.0, _f(ev.get("threat"))))
|
|
49
|
+
if _f(ev.get("connection_delta"), 0.0) > 0.10:
|
|
50
|
+
dims["trust"] = max(dims["trust"], min(1.0, 0.45 + _f(ev.get("connection_delta"))))
|
|
51
|
+
top = max(dims, key=lambda k: dims[k]) if dims else "neutral"
|
|
52
|
+
top_val = dims.get(top, 0.0)
|
|
53
|
+
if top_val < 0.08:
|
|
54
|
+
top = ("sadness" if v < -0.15 else ("joy" if v > 0.15 else "neutral"))
|
|
55
|
+
top_val = abs(v)
|
|
56
|
+
emotion_tag = top if top != "neutral" else "neutral"
|
|
57
|
+
intensity_tag = "high" if max(top_val, abs(v), a) > 0.62 else ("low" if max(top_val, abs(v), a) < 0.28 else "medium")
|
|
58
|
+
|
|
59
|
+
energy_tag = "low" if energy < 0.38 else ("high" if energy > 0.72 else "normal")
|
|
60
|
+
stress_tag = "overloaded" if stress > 0.60 else ("tense" if stress > 0.30 else "calm")
|
|
61
|
+
if emotion_tag in ("sadness", "loneliness"):
|
|
62
|
+
need = "be_heard"
|
|
63
|
+
elif emotion_tag in ("fear", "anger"):
|
|
64
|
+
need = "space" if emotion_tag == "fear" else "vent"
|
|
65
|
+
elif emotion_tag in ("joy", "trust"):
|
|
66
|
+
need = "share"
|
|
67
|
+
elif stress_tag == "overloaded" or energy_tag == "low":
|
|
68
|
+
need = "space"
|
|
69
|
+
else:
|
|
70
|
+
need = "talk"
|
|
71
|
+
|
|
72
|
+
if emotion_tag in ("fear", "anger", "sadness", "loneliness") or trust < 0.32:
|
|
73
|
+
distance = "far"
|
|
74
|
+
elif emotion_tag in ("joy", "trust") and trust > 0.45:
|
|
75
|
+
distance = "near"
|
|
76
|
+
else:
|
|
77
|
+
distance = "neutral"
|
|
78
|
+
|
|
79
|
+
if emotion_tag in ("sadness", "fear"):
|
|
80
|
+
tone = "soft"
|
|
81
|
+
elif emotion_tag == "anger":
|
|
82
|
+
tone = "blunt"
|
|
83
|
+
elif emotion_tag in ("joy", "surprise"):
|
|
84
|
+
tone = "playful"
|
|
85
|
+
elif emotion_tag == "loneliness":
|
|
86
|
+
tone = "quiet"
|
|
87
|
+
else:
|
|
88
|
+
tone = "neutral"
|
|
89
|
+
|
|
90
|
+
if stress_tag == "overloaded" or energy_tag == "low":
|
|
91
|
+
intent = "withdraw"
|
|
92
|
+
elif need == "share" and stress_tag != "overloaded":
|
|
93
|
+
intent = "approach"
|
|
94
|
+
elif emotion_tag in ("joy", "trust") and energy_tag != "low":
|
|
95
|
+
intent = "approach"
|
|
96
|
+
elif energy_tag == "high" and trust > 0.62 and stress_tag == "calm":
|
|
97
|
+
intent = "approach"
|
|
98
|
+
elif need == "be_heard" and energy_tag != "low":
|
|
99
|
+
intent = "hold"
|
|
100
|
+
else:
|
|
101
|
+
intent = "hold"
|
|
102
|
+
expression = "short" if intent == "withdraw" or stress_tag == "overloaded" else (
|
|
103
|
+
"open" if intent == "approach" and energy_tag != "low" else "normal")
|
|
104
|
+
q_budget = 1 if (expression == "open" and intent == "approach") or (
|
|
105
|
+
need in ("be_heard", "share") and expression != "short" and energy_tag != "low") else 0
|
|
106
|
+
proactive = 0.0
|
|
107
|
+
if intent == "approach":
|
|
108
|
+
proactive = 0.10 + 0.10 * max(0.0, _f(ro.get("engage_bias")))
|
|
109
|
+
elif intent == "withdraw":
|
|
110
|
+
proactive = -0.15
|
|
111
|
+
mode = str(b.get("private_mode") or "answer")
|
|
112
|
+
if intent == "withdraw" and mode == "answer":
|
|
113
|
+
mode = "brief"
|
|
114
|
+
return {
|
|
115
|
+
"emotion": emotion_tag,
|
|
116
|
+
"intensity": intensity_tag,
|
|
117
|
+
"need": need,
|
|
118
|
+
"distance": distance,
|
|
119
|
+
"tone": tone,
|
|
120
|
+
"intent": intent,
|
|
121
|
+
"energy": energy_tag,
|
|
122
|
+
"stress": stress_tag,
|
|
123
|
+
"expression": expression,
|
|
124
|
+
"question_budget": q_budget,
|
|
125
|
+
"proactive_bias": round(proactive, 3),
|
|
126
|
+
"mode": mode,
|
|
127
|
+
"valence": "positive" if v > 0.12 else ("negative" if v < -0.12 else "neutral"),
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def context(semantic: dict) -> str:
|
|
132
|
+
s = dict(semantic or {})
|
|
133
|
+
return ("emotion=%s intensity=%s need=%s distance=%s tone=%s "
|
|
134
|
+
"intent=%s expression=%s mode=%s" % (
|
|
135
|
+
s.get("emotion", "neutral"), s.get("intensity", "medium"),
|
|
136
|
+
s.get("need", "talk"), s.get("distance", "neutral"),
|
|
137
|
+
s.get("tone", "neutral"), s.get("intent", "hold"),
|
|
138
|
+
s.get("expression", "normal"), s.get("mode", "answer")))
|