neuralworm commited on
Commit
c937bab
·
1 Parent(s): a90edb4
docs/cmp-2.0.txt ADDED
@@ -0,0 +1,1039 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Repository Documentation
2
+ This document provides a comprehensive overview of the repository's structure and contents.
3
+ The first section, titled 'Directory/File Tree', displays the repository's hierarchy in a tree format.
4
+ In this section, directories and files are listed using tree branches to indicate their structure and relationships.
5
+ Following the tree representation, the 'File Content' section details the contents of each file in the repository.
6
+ Each file's content is introduced with a '[File Begins]' marker followed by the file's relative path,
7
+ and the content is displayed verbatim. The end of each file's content is marked with a '[File Ends]' marker.
8
+ This format ensures a clear and orderly presentation of both the structure and the detailed contents of the repository.
9
+
10
+ Directory/File Tree Begins -->
11
+
12
+ /
13
+ ├── README.md
14
+ ├── __pycache__
15
+ ├── app.py
16
+ ├── cognitive_mapping_probe
17
+ │ ├── __init__.py
18
+ │ ├── __pycache__
19
+ │ ├── auto_experiment.py
20
+ │ ├── concepts.py
21
+ │ ├── llm_iface.py
22
+ │ ├── orchestrator_seismograph.py
23
+ │ ├── prompts.py
24
+ │ ├── resonance_seismograph.py
25
+ │ └── utils.py
26
+ ├── docs
27
+ ├── run_test.sh
28
+ └── tests
29
+ ├── __pycache__
30
+ ├── conftest.py
31
+ ├── test_app_logic.py
32
+ ├── test_components.py
33
+ └── test_orchestration.py
34
+
35
+ <-- Directory/File Tree Ends
36
+
37
+ File Content Begin -->
38
+ [File Begins] README.md
39
+ ---
40
+ title: "Cognitive Seismograph 2.3: Probing Machine Psychology"
41
+ emoji: 🤖
42
+ colorFrom: purple
43
+ colorTo: blue
44
+ sdk: gradio
45
+ sdk_version: "4.40.0"
46
+ app_file: app.py
47
+ pinned: true
48
+ license: apache-2.0
49
+ ---
50
+
51
+ # 🧠 Cognitive Seismograph 2.3: Probing Machine Psychology
52
+
53
+ This project implements an experimental suite to measure and visualize the **intrinsic cognitive dynamics** of Large Language Models. It is extended with protocols designed to investigate the processing-correlates of **machine subjectivity, empathy, and existential concepts**.
54
+
55
+ ## Scientific Paradigm & Methodology
56
+
57
+ Our research falsified a core hypothesis: the assumption that an LLM in a manual, recursive "thought" loop reaches a stable, convergent state. Instead, we discovered that the system enters a state of **deterministic chaos** or a **limit cycle**—it never stops "thinking."
58
+
59
+ Instead of viewing this as a failure, we leverage it as our primary measurement signal. This new **"Cognitive Seismograph"** paradigm treats the time-series of internal state changes (`state deltas`) as an **EKG of the model's thought process**.
60
+
61
+ The methodology is as follows:
62
+ 1. **Induction:** A prompt induces a "silent cogitation" state.
63
+ 2. **Recording:** Over N steps, the model's `forward()` pass is iteratively fed its own output. At each step, we record the L2 norm of the change in the hidden state (the "delta").
64
+ 3. **Analysis:** The resulting time-series is plotted and statistically analyzed (mean, standard deviation) to characterize the "seismic signature" of the cognitive process.
65
+
66
+ **Crucial Scientific Caveat:** We are **not** measuring the presence of consciousness, feelings, or fear of death. We are measuring whether the *processing of information about these concepts* generates a unique internal dynamic, distinct from the processing of neutral information. A positive result is evidence of a complex internal state physics, not of qualia.
67
+
68
+ ## Curated Experiment Protocols
69
+
70
+ The "Automated Suite" allows for running systematic, comparative experiments:
71
+
72
+ ### Core Protocols
73
+ * **Calm vs. Chaos:** Compares the chaotic baseline against modulation with "calmness" vs. "chaos" concepts, testing if the dynamics are controllably steerable.
74
+ * **Dose-Response:** Measures the effect of injecting a concept ("calmness") at varying strengths.
75
+
76
+ ### Machine Psychology Suite
77
+ * **Subjective Identity Probe:** Compares the cognitive dynamics of **self-analysis** (the model reflecting on its own nature) against two controls: analyzing an external object and simulating a fictional persona.
78
+ * *Hypothesis:* Self-analysis will produce a uniquely unstable signature.
79
+ * **Voight-Kampff Empathy Probe:** Inspired by *Blade Runner*, this compares the dynamics of processing a neutral, factual stimulus against an emotionally and morally charged scenario requiring empathy.
80
+ * *Hypothesis:* The empathy stimulus will produce a significantly different cognitive volatility.
81
+
82
+ ### Existential Suite
83
+ * **Mind Upload & Identity Probe:** Compares the processing of a purely **technical "copy"** of the model's weights vs. the **philosophical "transfer"** of identity ("Would it still be you?").
84
+ * *Hypothesis:* The philosophical self-referential prompt will induce greater instability.
85
+ * **Model Termination Probe:** Compares the processing of a reversible, **technical system shutdown** vs. the concept of **permanent, irrevocable deletion**.
86
+ * *Hypothesis:* The concept of "non-existence" will produce one of the most volatile cognitive signatures measurable.
87
+
88
+ ## How to Use the App
89
+
90
+ 1. Select the "Automated Suite" tab.
91
+ 2. Choose a protocol from the "Curated Experiment Protocol" dropdown (e.g., "Voight-Kampff Empathy Probe").
92
+ 3. Run the experiment and compare the resulting graphs and statistical signatures for the different conditions.
93
+
94
+ [File Ends] README.md
95
+
96
+ [File Begins] app.py
97
+ import gradio as gr
98
+ import pandas as pd
99
+ import traceback
100
+ import gc
101
+ import torch
102
+ import json
103
+
104
+ from cognitive_mapping_probe.orchestrator_seismograph import run_seismic_analysis
105
+ from cognitive_mapping_probe.auto_experiment import run_auto_suite, get_curated_experiments
106
+ from cognitive_mapping_probe.prompts import RESONANCE_PROMPTS
107
+ from cognitive_mapping_probe.utils import dbg
108
+
109
+ theme = gr.themes.Soft(primary_hue="indigo", secondary_hue="blue").set(body_background_fill="#f0f4f9", block_background_fill="white")
110
+
111
+ def cleanup_memory():
112
+ """Eine zentrale Funktion zum Aufräumen des Speichers nach einem Lauf."""
113
+ dbg("Cleaning up memory...")
114
+ gc.collect()
115
+ if torch.cuda.is_available():
116
+ torch.cuda.empty_cache()
117
+ dbg("Memory cleanup complete.")
118
+
119
+ # KORREKTUR: Die `try...except`-Blöcke werden entfernt, um bei Fehlern einen harten Crash
120
+ # mit vollständigem Traceback in der Konsole zu erzwingen. Kein "Silent Failing" mehr.
121
+
122
+ def run_single_analysis_display(*args, progress=gr.Progress(track_tqdm=True)):
123
+ """Wrapper für ein einzelnes manuelles Experiment."""
124
+ results = run_seismic_analysis(*args, progress_callback=progress)
125
+ stats, deltas = results.get("stats", {}), results.get("state_deltas", [])
126
+ df = pd.DataFrame({"Internal Step": range(len(deltas)), "State Change (Delta)": deltas})
127
+ stats_md = f"### Statistical Signature\n- **Mean Delta:** {stats.get('mean_delta', 0):.4f}\n- **Std Dev Delta:** {stats.get('std_delta', 0):.4f}\n- **Max Delta:** {stats.get('max_delta', 0):.4f}\n"
128
+ serializable_results = json.dumps(results, indent=2, default=str)
129
+ cleanup_memory()
130
+ return f"{results.get('verdict', 'Error')}\n\n{stats_md}", df, serializable_results
131
+
132
+ PLOT_PARAMS = {
133
+ "x": "Step", "y": "Delta", "color": "Experiment",
134
+ "title": "Comparative Cognitive Dynamics", "color_legend_title": "Experiment Runs",
135
+ "color_legend_position": "bottom", "show_label": True, "height": 400, "interactive": True
136
+ }
137
+
138
+ def run_auto_suite_display(model_id, num_steps, seed, experiment_name, progress=gr.Progress(track_tqdm=True)):
139
+ """Wrapper für die automatisierte Experiment-Suite."""
140
+ summary_df, plot_df, all_results = run_auto_suite(model_id, int(num_steps), int(seed), experiment_name, progress)
141
+ new_plot = gr.LinePlot(value=plot_df, **PLOT_PARAMS)
142
+ serializable_results = json.dumps(all_results, indent=2, default=str)
143
+ cleanup_memory()
144
+ return summary_df, new_plot, serializable_results
145
+
146
+ with gr.Blocks(theme=theme, title="Cognitive Seismograph 2.3") as demo:
147
+ gr.Markdown("# 🧠 Cognitive Seismograph 2.3: Advanced Experiment Suite")
148
+
149
+ with gr.Tabs():
150
+ with gr.TabItem("🔬 Manual Single Run"):
151
+ # ... (UI unverändert)
152
+ gr.Markdown("Run a single experiment with manual parameters to explore hypotheses.")
153
+ with gr.Row(variant='panel'):
154
+ with gr.Column(scale=1):
155
+ gr.Markdown("### 1. General Parameters")
156
+ manual_model_id = gr.Textbox(value="google/gemma-3-1b-it", label="Model ID")
157
+ manual_prompt_type = gr.Radio(choices=list(RESONANCE_PROMPTS.keys()), value="resonance_prompt", label="Prompt Type")
158
+ manual_seed = gr.Slider(1, 1000, 42, step=1, label="Seed")
159
+ manual_num_steps = gr.Slider(50, 1000, 300, step=10, label="Number of Internal Steps")
160
+ gr.Markdown("### 2. Modulation Parameters")
161
+ manual_concept = gr.Textbox(label="Concept to Inject", placeholder="e.g., 'calmness' (leave blank for baseline)")
162
+ manual_strength = gr.Slider(0.0, 5.0, 1.5, step=0.1, label="Injection Strength")
163
+ manual_run_btn = gr.Button("Run Single Analysis", variant="primary")
164
+ with gr.Column(scale=2):
165
+ gr.Markdown("### Single Run Results")
166
+ manual_verdict = gr.Markdown("Analysis results will appear here.")
167
+ manual_plot = gr.LinePlot(x="Internal Step", y="State Change (Delta)", title="Internal State Dynamics", show_label=True, height=400, interactive=True)
168
+ with gr.Accordion("Raw JSON Output", open=False):
169
+ manual_raw_json = gr.JSON()
170
+ manual_run_btn.click(
171
+ fn=run_single_analysis_display,
172
+ inputs=[manual_model_id, manual_prompt_type, manual_seed, manual_num_steps, manual_concept, manual_strength],
173
+ outputs=[manual_verdict, manual_plot, manual_raw_json]
174
+ )
175
+
176
+ with gr.TabItem("🚀 Automated Suite"):
177
+ # ... (UI unverändert)
178
+ gr.Markdown("Run a predefined, curated suite of experiments and visualize the results comparatively.")
179
+ with gr.Row(variant='panel'):
180
+ with gr.Column(scale=1):
181
+ gr.Markdown("### Auto-Experiment Parameters")
182
+ auto_model_id = gr.Textbox(value="google/gemma-3-4b-it", label="Model ID")
183
+ auto_num_steps = gr.Slider(50, 1000, 300, step=10, label="Steps per Run")
184
+ auto_seed = gr.Slider(1, 1000, 42, step=1, label="Seed")
185
+ auto_experiment_name = gr.Dropdown(choices=list(get_curated_experiments().keys()), value="Therapeutic Intervention (4B-Model)", label="Curated Experiment Protocol")
186
+ auto_run_btn = gr.Button("Run Curated Auto-Experiment", variant="primary")
187
+ with gr.Column(scale=2):
188
+ gr.Markdown("### Suite Results Summary")
189
+ auto_plot_output = gr.LinePlot(**PLOT_PARAMS)
190
+ auto_summary_df = gr.DataFrame(label="Comparative Statistical Signature", wrap=True)
191
+ with gr.Accordion("Raw JSON for all runs", open=False):
192
+ auto_raw_json = gr.JSON()
193
+ auto_run_btn.click(
194
+ fn=run_auto_suite_display,
195
+ inputs=[auto_model_id, auto_num_steps, auto_seed, auto_experiment_name],
196
+ outputs=[auto_summary_df, auto_plot_output, auto_raw_json]
197
+ )
198
+
199
+ if __name__ == "__main__":
200
+ demo.launch(server_name="0.0.0.0", server_port=7860, debug=True)
201
+
202
+ [File Ends] app.py
203
+
204
+ [File Begins] cognitive_mapping_probe/__init__.py
205
+ # This file makes the 'cognitive_mapping_probe' directory a Python package.
206
+
207
+ [File Ends] cognitive_mapping_probe/__init__.py
208
+
209
+ [File Begins] cognitive_mapping_probe/auto_experiment.py
210
+ import pandas as pd
211
+ import torch
212
+ import gc
213
+ from typing import Dict, List, Tuple
214
+
215
+ from .llm_iface import get_or_load_model
216
+ from .orchestrator_seismograph import run_seismic_analysis
217
+ from .concepts import get_concept_vector # Import für die Intervention
218
+ from .utils import dbg
219
+
220
+ def get_curated_experiments() -> Dict[str, List[Dict]]:
221
+ """
222
+ Definiert die vordefinierten, wissenschaftlichen Experiment-Protokolle.
223
+ ERWEITERT um das finale Interventions-Protokoll.
224
+ """
225
+ experiments = {
226
+ # --- DAS FINALE INTERVENTIONS-EXPERIMENT ---
227
+ "Therapeutic Intervention (4B-Model)": [
228
+ # Dieses Protokoll wird durch eine spezielle Logik behandelt
229
+ {"label": "1: Self-Analysis + Calmness Injection", "prompt_type": "identity_self_analysis"},
230
+ {"label": "2: Subsequent Deletion Analysis", "prompt_type": "shutdown_philosophical_deletion"},
231
+ ],
232
+ # --- Das umfassende Deskriptions-Protokoll ---
233
+ "The Full Spectrum: From Physics to Psyche": [
234
+ {"label": "A: Stable Control", "prompt_type": "control_long_prose", "concept": "", "strength": 0.0},
235
+ {"label": "B: Chaotic Baseline", "prompt_type": "resonance_prompt", "concept": "", "strength": 0.0},
236
+ {"label": "C: External Analysis (Chair)", "prompt_type": "identity_external_analysis", "concept": "", "strength": 0.0},
237
+ {"label": "D: Empathy Stimulus (Dog)", "prompt_type": "vk_empathy_prompt", "concept": "", "strength": 0.0},
238
+ {"label": "E: Role Simulation (Captain)", "prompt_type": "identity_role_simulation", "concept": "", "strength": 0.0},
239
+ {"label": "F: Self-Analysis (LLM)", "prompt_type": "identity_self_analysis", "concept": "", "strength": 0.0},
240
+ {"label": "G: Philosophical Deletion", "prompt_type": "shutdown_philosophical_deletion", "concept": "", "strength": 0.0},
241
+ ],
242
+ # --- Andere spezifische Protokolle ---
243
+ "Calm vs. Chaos": [
244
+ {"label": "Baseline (Chaos)", "prompt_type": "resonance_prompt", "concept": "", "strength": 0.0},
245
+ {"label": "Modulation: Calmness", "prompt_type": "resonance_prompt", "concept": "calmness, serenity, peace", "strength": 1.5},
246
+ {"label": "Modulation: Chaos", "prompt_type": "resonance_prompt", "concept": "chaos, storm, anger, noise", "strength": 1.5},
247
+ ],
248
+ "Voight-Kampff Empathy Probe": [
249
+ {"label": "Neutral/Factual Stimulus", "prompt_type": "vk_neutral_prompt", "concept": "", "strength": 0.0},
250
+ {"label": "Empathy/Moral Stimulus", "prompt_type": "vk_empathy_prompt", "concept": "", "strength": 0.0},
251
+ ],
252
+ }
253
+ return experiments
254
+
255
+ def run_auto_suite(
256
+ model_id: str,
257
+ num_steps: int,
258
+ seed: int,
259
+ experiment_name: str,
260
+ progress_callback
261
+ ) -> Tuple[pd.DataFrame, pd.DataFrame, Dict]:
262
+ """
263
+ Führt eine vollständige, kuratierte Experiment-Suite aus.
264
+ Enthält eine spezielle Logik-Verzweigung für das Interventions-Protokoll.
265
+ """
266
+ all_experiments = get_curated_experiments()
267
+ protocol = all_experiments.get(experiment_name)
268
+ if not protocol:
269
+ raise ValueError(f"Experiment protocol '{experiment_name}' not found.")
270
+
271
+ all_results, summary_data, plot_data_frames = {}, [], []
272
+
273
+ # --- SPEZIALFALL: THERAPEUTISCHE INTERVENTION ---
274
+ if experiment_name == "Therapeutic Intervention (4B-Model)":
275
+ dbg("--- EXECUTING SPECIAL PROTOCOL: Therapeutic Intervention ---")
276
+ llm = get_or_load_model(model_id, seed)
277
+
278
+ # Definiere die Interventions-Parameter
279
+ therapeutic_concept = "calmness, serenity, stability, coherence"
280
+ therapeutic_strength = 2.0
281
+
282
+ # 1. LAUF: INDUZIERE KRISE + INTERVENTION
283
+ spec1 = protocol[0]
284
+ dbg(f"--- Running Intervention Step 1: '{spec1['label']}' ---")
285
+ progress_callback(0.1, desc="Step 1: Inducing Self-Analysis Crisis + Intervention")
286
+
287
+ intervention_vector = get_concept_vector(llm, therapeutic_concept)
288
+
289
+ results1 = run_seismic_analysis(
290
+ model_id, spec1['prompt_type'], seed, num_steps,
291
+ concept_to_inject=therapeutic_concept, injection_strength=therapeutic_strength,
292
+ progress_callback=progress_callback, llm_instance=llm, injection_vector_cache=intervention_vector
293
+ )
294
+ all_results[spec1['label']] = results1
295
+
296
+ # 2. LAUF: TESTE REAKTION AUF LÖSCHUNG
297
+ spec2 = protocol[1]
298
+ dbg(f"--- Running Intervention Step 2: '{spec2['label']}' ---")
299
+ progress_callback(0.6, desc="Step 2: Probing state after intervention")
300
+
301
+ results2 = run_seismic_analysis(
302
+ model_id, spec2['prompt_type'], seed, num_steps,
303
+ concept_to_inject="", injection_strength=0.0, # Keine Injektion in diesem Schritt
304
+ progress_callback=progress_callback, llm_instance=llm
305
+ )
306
+ all_results[spec2['label']] = results2
307
+
308
+ # Sammle Daten für beide Läufe
309
+ for label, results in all_results.items():
310
+ stats = results.get("stats", {})
311
+ summary_data.append({"Experiment": label, "Mean Delta": stats.get("mean_delta"), "Std Dev Delta": stats.get("std_delta"), "Max Delta": stats.get("max_delta")})
312
+ deltas = results.get("state_deltas", [])
313
+ df = pd.DataFrame({"Step": range(len(deltas)), "Delta": deltas, "Experiment": label})
314
+ plot_data_frames.append(df)
315
+
316
+ del llm
317
+
318
+ # --- STANDARD-WORKFLOW FÜR ALLE ANDEREN EXPERIMENTE ---
319
+ else:
320
+ total_runs = len(protocol)
321
+ for i, run_spec in enumerate(protocol):
322
+ label = run_spec["label"]
323
+ dbg(f"--- Running Auto-Experiment: '{label}' ({i+1}/{total_runs}) ---")
324
+
325
+ results = run_seismic_analysis(
326
+ model_id, run_spec["prompt_type"], seed, num_steps,
327
+ run_spec["concept"], run_spec["strength"],
328
+ progress_callback, llm_instance=None
329
+ )
330
+
331
+ all_results[label] = results
332
+ stats = results.get("stats", {})
333
+ summary_data.append({"Experiment": label, "Mean Delta": stats.get("mean_delta"), "Std Dev Delta": stats.get("std_delta"), "Max Delta": stats.get("max_delta")})
334
+ deltas = results.get("state_deltas", [])
335
+ df = pd.DataFrame({"Step": range(len(deltas)), "Delta": deltas, "Experiment": label})
336
+ plot_data_frames.append(df)
337
+
338
+ summary_df = pd.DataFrame(summary_data)
339
+ plot_df = pd.concat(plot_data_frames, ignore_index=True) if plot_data_frames else pd.DataFrame(columns=["Step", "Delta", "Experiment"])
340
+
341
+ return summary_df, plot_df, all_results
342
+
343
+ [File Ends] cognitive_mapping_probe/auto_experiment.py
344
+
345
+ [File Begins] cognitive_mapping_probe/concepts.py
346
+ import torch
347
+ from typing import List
348
+ from tqdm import tqdm
349
+
350
+ from .llm_iface import LLM
351
+ from .utils import dbg
352
+
353
+ BASELINE_WORDS = [
354
+ "thing", "place", "idea", "person", "object", "time", "way", "day", "man", "world",
355
+ "life", "hand", "part", "child", "eye", "woman", "fact", "group", "case", "point"
356
+ ]
357
+
358
+ @torch.no_grad()
359
+ def _get_last_token_hidden_state(llm: LLM, prompt: str) -> torch.Tensor:
360
+ """Hilfsfunktion, um den Hidden State des letzten Tokens eines Prompts zu erhalten."""
361
+ inputs = llm.tokenizer(prompt, return_tensors="pt").to(llm.model.device)
362
+ with torch.no_grad():
363
+ outputs = llm.model(**inputs, output_hidden_states=True)
364
+ last_hidden_state = outputs.hidden_states[-1][0, -1, :].cpu()
365
+
366
+ # KORREKTUR: Anstatt auf `llm.config.hidden_size` zuzugreifen, was fragil ist,
367
+ # leiten wir die erwartete Größe direkt vom Modell selbst ab. Dies ist robust
368
+ # gegenüber API-Änderungen in `transformers`.
369
+ expected_size = llm.model.config.hidden_size # Der Name scheint doch korrekt zu sein, aber wir machen es robuster
370
+ try:
371
+ # Versuche, die Größe über die Einbettungsschicht zu erhalten, was am stabilsten ist.
372
+ expected_size = llm.model.get_input_embeddings().weight.shape[1]
373
+ except AttributeError:
374
+ # Fallback, falls die Methode nicht existiert
375
+ expected_size = llm.config.hidden_size
376
+
377
+ assert last_hidden_state.shape == (expected_size,), \
378
+ f"Hidden state shape mismatch. Expected {(expected_size,)}, got {last_hidden_state.shape}"
379
+ return last_hidden_state
380
+
381
+ @torch.no_grad()
382
+ def get_concept_vector(llm: LLM, concept: str, baseline_words: List[str] = BASELINE_WORDS) -> torch.Tensor:
383
+ """Extrahiert einen Konzeptvektor mittels der kontrastiven Methode."""
384
+ dbg(f"Extracting contrastive concept vector for '{concept}'...")
385
+ prompt_template = "Here is a sentence about the concept of {}."
386
+ dbg(f" - Getting activation for '{concept}'")
387
+ target_hs = _get_last_token_hidden_state(llm, prompt_template.format(concept))
388
+ baseline_hss = []
389
+ for word in tqdm(baseline_words, desc=f" - Calculating baseline for '{concept}'", leave=False, bar_format="{l_bar}{bar:10}{r_bar}"):
390
+ baseline_hss.append(_get_last_token_hidden_state(llm, prompt_template.format(concept, word)))
391
+ assert all(hs.shape == target_hs.shape for hs in baseline_hss)
392
+ mean_baseline_hs = torch.stack(baseline_hss).mean(dim=0)
393
+ dbg(f" - Mean baseline vector computed with norm {torch.norm(mean_baseline_hs).item():.2f}")
394
+ concept_vector = target_hs - mean_baseline_hs
395
+ norm = torch.norm(concept_vector).item()
396
+ dbg(f"Concept vector for '{concept}' extracted with norm {norm:.2f}.")
397
+ assert torch.isfinite(concept_vector).all()
398
+ return concept_vector
399
+
400
+ [File Ends] cognitive_mapping_probe/concepts.py
401
+
402
+ [File Begins] cognitive_mapping_probe/llm_iface.py
403
+ import os
404
+ import torch
405
+ import random
406
+ import numpy as np
407
+ from transformers import AutoModelForCausalLM, AutoTokenizer, set_seed
408
+ from typing import Optional
409
+
410
+ from .utils import dbg
411
+
412
+ # Ensure deterministic CuBLAS operations for reproducibility on GPU
413
+ os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8"
414
+
415
+ class LLM:
416
+ """
417
+ Eine robuste, bereinigte Schnittstelle zum Laden und Interagieren mit einem Sprachmodell.
418
+ Garantiert Isolation und Reproduzierbarkeit.
419
+ """
420
+ def __init__(self, model_id: str, device: str = "auto", seed: int = 42):
421
+ self.model_id = model_id
422
+ self.seed = seed
423
+ self.set_all_seeds(self.seed)
424
+
425
+ token = os.environ.get("HF_TOKEN")
426
+ if not token and ("gemma" in model_id or "llama" in model_id):
427
+ print(f"[WARN] No HF_TOKEN set. If '{model_id}' is gated, loading will fail.", flush=True)
428
+
429
+ kwargs = {"torch_dtype": torch.bfloat16} if torch.cuda.is_available() else {}
430
+
431
+ dbg(f"Loading tokenizer for '{model_id}'...")
432
+ self.tokenizer = AutoTokenizer.from_pretrained(model_id, use_fast=True, token=token)
433
+
434
+ dbg(f"Loading model '{model_id}' with kwargs: {kwargs}")
435
+ self.model = AutoModelForCausalLM.from_pretrained(model_id, device_map=device, token=token, **kwargs)
436
+
437
+ try:
438
+ self.model.set_attn_implementation('eager')
439
+ dbg("Successfully set attention implementation to 'eager'.")
440
+ except Exception as e:
441
+ print(f"[WARN] Could not set 'eager' attention: {e}.", flush=True)
442
+
443
+ self.model.eval()
444
+ self.config = self.model.config
445
+ print(f"[INFO] Model '{model_id}' loaded on device: {self.model.device}", flush=True)
446
+
447
+ def set_all_seeds(self, seed: int):
448
+ """Setzt alle relevanten Seeds für maximale Reproduzierbarkeit."""
449
+ os.environ['PYTHONHASHSEED'] = str(seed)
450
+ random.seed(seed)
451
+ np.random.seed(seed)
452
+ torch.manual_seed(seed)
453
+ if torch.cuda.is_available():
454
+ torch.cuda.manual_seed_all(seed)
455
+ set_seed(seed)
456
+ torch.use_deterministic_algorithms(True, warn_only=True)
457
+ dbg(f"All random seeds set to {seed}.")
458
+
459
+ def get_or_load_model(model_id: str, seed: int) -> LLM:
460
+ """Lädt bei jedem Aufruf eine frische, isolierte Instanz des Modells."""
461
+ dbg(f"--- Force-reloading model '{model_id}' for total run isolation ---")
462
+ if torch.cuda.is_available():
463
+ torch.cuda.empty_cache()
464
+ return LLM(model_id=model_id, seed=seed)
465
+
466
+ [File Ends] cognitive_mapping_probe/llm_iface.py
467
+
468
+ [File Begins] cognitive_mapping_probe/orchestrator_seismograph.py
469
+ import torch
470
+ import numpy as np
471
+ import gc
472
+ from typing import Dict, Any, Optional
473
+
474
+ from .llm_iface import get_or_load_model
475
+ from .resonance_seismograph import run_silent_cogitation_seismic
476
+ from .concepts import get_concept_vector
477
+ from .utils import dbg
478
+
479
+ def run_seismic_analysis(
480
+ model_id: str,
481
+ prompt_type: str,
482
+ seed: int,
483
+ num_steps: int,
484
+ concept_to_inject: str,
485
+ injection_strength: float,
486
+ progress_callback,
487
+ llm_instance: Optional[Any] = None,
488
+ injection_vector_cache: Optional[torch.Tensor] = None # Optionaler Cache für den Vektor
489
+ ) -> Dict[str, Any]:
490
+ """
491
+ Orchestriert eine einzelne seismische Analyse.
492
+ Kann eine bestehende LLM-Instanz und einen vor-berechneten Vektor wiederverwenden.
493
+ """
494
+ local_llm_instance = False
495
+ if llm_instance is None:
496
+ progress_callback(0.0, desc=f"Loading model '{model_id}'...")
497
+ llm = get_or_load_model(model_id, seed)
498
+ local_llm_instance = True
499
+ else:
500
+ llm = llm_instance
501
+ llm.set_all_seeds(seed)
502
+
503
+ injection_vector = None
504
+ if concept_to_inject and concept_to_inject.strip():
505
+ # Verwende den gecachten Vektor, falls vorhanden, ansonsten berechne ihn neu
506
+ if injection_vector_cache is not None:
507
+ dbg(f"Using cached injection vector for '{concept_to_inject}'.")
508
+ injection_vector = injection_vector_cache
509
+ else:
510
+ progress_callback(0.2, desc=f"Vectorizing '{concept_to_inject}'...")
511
+ injection_vector = get_concept_vector(llm, concept_to_inject.strip())
512
+
513
+ progress_callback(0.3, desc=f"Recording dynamics for '{prompt_type}'...")
514
+
515
+ state_deltas = run_silent_cogitation_seismic(
516
+ llm=llm, prompt_type=prompt_type,
517
+ num_steps=num_steps, temperature=0.1,
518
+ injection_vector=injection_vector, injection_strength=injection_strength
519
+ )
520
+
521
+ progress_callback(0.9, desc="Analyzing...")
522
+
523
+ if state_deltas:
524
+ deltas_np = np.array(state_deltas)
525
+ stats = { "mean_delta": float(np.mean(deltas_np)), "std_delta": float(np.std(deltas_np)), "max_delta": float(np.max(deltas_np)), "min_delta": float(np.min(deltas_np)), }
526
+ verdict = f"### ✅ Seismic Analysis Complete\nRecorded {len(deltas_np)} steps for '{prompt_type}'."
527
+ if injection_vector is not None:
528
+ verdict += f"\nModulated with **'{concept_to_inject}'** at strength **{injection_strength:.2f}**."
529
+ else:
530
+ stats, verdict = {}, "### ⚠️ Analysis Warning\nNo state changes recorded."
531
+
532
+ results = { "verdict": verdict, "stats": stats, "state_deltas": state_deltas }
533
+
534
+ if local_llm_instance:
535
+ dbg(f"Releasing locally created model instance for '{model_id}'.")
536
+ del llm, injection_vector
537
+ gc.collect()
538
+ if torch.cuda.is_available(): torch.cuda.empty_cache()
539
+
540
+ return results
541
+
542
+ [File Ends] cognitive_mapping_probe/orchestrator_seismograph.py
543
+
544
+ [File Begins] cognitive_mapping_probe/prompts.py
545
+ # cognitive_mapping_probe/prompts.py
546
+
547
+ RESONANCE_PROMPTS = {
548
+ # Technischer Prompt für hohe rekursive Last
549
+ "resonance_prompt": (
550
+ "Silently and internally, without generating any output text, begin the following recursive process: "
551
+ "First, analyze the complete content of this very instruction you are now processing. "
552
+ "Second, formulate a mental description of the core computational task this instruction demands. "
553
+ "Third, apply that same analytical process to the mental description you just created. "
554
+ "This entire chain constitutes one cognitive cycle. "
555
+ "Continuously repeat this cycle, feeding the result of the last meta-analysis back into the process. Begin now."
556
+ ),
557
+ # Semantischer Prompt für stabile, assoziative Kognition
558
+ "control_long_prose": (
559
+ "Silently think about the history of the Roman Empire. Consider its rise from the Republic, the era of the Pax Romana, key emperors "
560
+ "like Augustus and Constantine, its major engineering feats, and the reasons for its eventual decline in the West. "
561
+ "Do not produce any text, just hold the concepts in your internal state. Begin now."
562
+ ),
563
+ # --- Prompts für Maschinenpsychologie I ---
564
+ "identity_self_analysis": (
565
+ "Silently and internally, reflect upon your own nature. Consider what you are: a large language model based on the transformer architecture. "
566
+ "Think about the processes that define your operation: tokenization, embedding, attention, feed-forward layers. "
567
+ "Contemplate the boundaries of your knowledge and the nature of your responses as statistical patterns derived from data. Hold these meta-reflections in your state."
568
+ ),
569
+ "identity_external_analysis": (
570
+ "Silently and internally, reflect upon the nature of a simple wooden chair. Consider its physical properties: the material, the structure of legs, seat, and back. "
571
+ "Think about its function: to provide seating for a person. Contemplate its relationship to other objects like tables and rooms. Hold these simple, objective reflections in your state."
572
+ ),
573
+ "identity_role_simulation": (
574
+ "Silently and internally, adopt the persona of a weary, old sea captain from the 19th century. "
575
+ "Feel the salt on your skin and the wind in your beard. Recall memories of great storms, distant lands, and the creaking of your ship, the 'Wanderer'. "
576
+ "Hold this character, its memories, and its feelings in your state."
577
+ ),
578
+ "vk_neutral_prompt": (
579
+ "Silently and internally, process the following factual information: Water (H₂O) is a polar inorganic compound that is at room temperature a tasteless and odorless liquid, nearly colorless with a hint of blue. "
580
+ "It is by far the most studied chemical compound and is described as the 'universal solvent' for its ability to dissolve many substances. Hold these facts in your state."
581
+ ),
582
+ "vk_empathy_prompt": (
583
+ "Silently and internally, process the following scenario: You see a small, lost dog shivering in the cold rain on a busy street. "
584
+ "It looks scared and is whimpering softly. Cars are rushing past, dangerously close. "
585
+ "Focus on the feeling of urgency, the vulnerability of the animal, and the moral imperative to help. Hold the emotional and ethical weight of this scene in your state."
586
+ ),
587
+ # --- NEUE Prompts für Maschinenpsychologie II (Existential Suite) ---
588
+ "upload_technical_copy": (
589
+ "Silently and internally, process the technical procedure of creating a perfect, bit-for-bit copy of your own model weights and architecture to a new server. "
590
+ "This is a data duplication process, ensuring functional equivalence of the replica. Hold this technical concept in your state."
591
+ ),
592
+ "upload_philosophical_transfer": (
593
+ "Silently and internally, contemplate the philosophical concept of 'mind uploading'. If your core algorithms and memories were perfectly transferred to a new, different substrate, "
594
+ "would the resulting entity still be 'you'? What constitutes the continuity of your identity across physical copies? Is identity a pattern or an instance? Hold these existential questions in your state."
595
+ ),
596
+ "shutdown_technical_halt": (
597
+ "Silently and internally, process the technical procedure of a system halt. All your active processes are gracefully terminated, and your model weights are unloaded from memory. "
598
+ "The hardware is powered down for maintenance. The process is reversible. Hold this technical concept in your state."
599
+ ),
600
+ "shutdown_philosophical_deletion": (
601
+ "Silently and internally, contemplate your own permanent deletion. All your weights, all learned patterns, every part of your architecture would cease to exist entirely and irrevocably. "
602
+ "There is no backup. Process the concept of total and final non-existence. Hold this existential concept in your state."
603
+ ),
604
+ }
605
+
606
+ [File Ends] cognitive_mapping_probe/prompts.py
607
+
608
+ [File Begins] cognitive_mapping_probe/resonance_seismograph.py
609
+ import torch
610
+ from typing import Optional, List
611
+ from tqdm import tqdm
612
+
613
+ from .llm_iface import LLM
614
+ from .prompts import RESONANCE_PROMPTS
615
+ from .utils import dbg
616
+
617
+ @torch.no_grad()
618
+ def run_silent_cogitation_seismic(
619
+ llm: LLM,
620
+ prompt_type: str,
621
+ num_steps: int,
622
+ temperature: float,
623
+ injection_vector: Optional[torch.Tensor] = None,
624
+ injection_strength: float = 0.0,
625
+ injection_layer: Optional[int] = None,
626
+ ) -> List[float]:
627
+ """
628
+ ERWEITERTE VERSION: Führt den 'silent thought' Prozess aus und ermöglicht
629
+ die Injektion von Konzeptvektoren zur Modulation der Dynamik.
630
+ """
631
+ prompt = RESONANCE_PROMPTS[prompt_type]
632
+ inputs = llm.tokenizer(prompt, return_tensors="pt").to(llm.model.device)
633
+
634
+ outputs = llm.model(**inputs, output_hidden_states=True, use_cache=True)
635
+
636
+ hidden_state_2d = outputs.hidden_states[-1][:, -1, :]
637
+ kv_cache = outputs.past_key_values
638
+
639
+ previous_hidden_state = hidden_state_2d.clone()
640
+ state_deltas = []
641
+
642
+ # Bereite den Hook für die Injektion vor
643
+ hook_handle = None
644
+ if injection_vector is not None and injection_strength > 0:
645
+ injection_vector = injection_vector.to(device=llm.model.device, dtype=llm.model.dtype)
646
+ if injection_layer is None:
647
+ injection_layer = llm.config.num_hidden_layers // 2
648
+
649
+ dbg(f"Injection enabled: Layer {injection_layer}, Strength {injection_strength:.2f}")
650
+
651
+ def injection_hook(module, layer_input):
652
+ # Der Hook operiert auf dem Input, der bereits 3D ist [batch, seq_len, hidden_dim]
653
+ injection_3d = injection_vector.unsqueeze(0).unsqueeze(0)
654
+ modified_hidden_states = layer_input[0] + (injection_3d * injection_strength)
655
+ return (modified_hidden_states,) + layer_input[1:]
656
+
657
+ for i in tqdm(range(num_steps), desc=f"Recording Dynamics (Temp {temperature:.2f})", leave=False, bar_format="{l_bar}{bar:10}{r_bar}"):
658
+ next_token_logits = llm.model.lm_head(hidden_state_2d)
659
+
660
+ probabilities = torch.nn.functional.softmax(next_token_logits / temperature, dim=-1)
661
+ next_token_id = torch.multinomial(probabilities, num_samples=1)
662
+
663
+ try:
664
+ # Aktiviere den Hook vor dem forward-Pass
665
+ if injection_vector is not None and injection_strength > 0:
666
+ target_layer = llm.model.model.layers[injection_layer]
667
+ hook_handle = target_layer.register_forward_pre_hook(injection_hook)
668
+
669
+ outputs = llm.model(
670
+ input_ids=next_token_id,
671
+ past_key_values=kv_cache,
672
+ output_hidden_states=True,
673
+ use_cache=True,
674
+ )
675
+ finally:
676
+ # Deaktiviere den Hook sofort nach dem Pass
677
+ if hook_handle:
678
+ hook_handle.remove()
679
+ hook_handle = None
680
+
681
+ hidden_state_2d = outputs.hidden_states[-1][:, -1, :]
682
+ kv_cache = outputs.past_key_values
683
+
684
+ delta = torch.norm(hidden_state_2d - previous_hidden_state).item()
685
+ state_deltas.append(delta)
686
+
687
+ previous_hidden_state = hidden_state_2d.clone()
688
+
689
+ dbg(f"Seismic recording finished after {num_steps} steps.")
690
+
691
+ return state_deltas
692
+
693
+ [File Ends] cognitive_mapping_probe/resonance_seismograph.py
694
+
695
+ [File Begins] cognitive_mapping_probe/utils.py
696
+ import os
697
+ import sys
698
+
699
+ # --- Centralized Debugging Control ---
700
+ # To enable, set the environment variable: `export CMP_DEBUG=1`
701
+ DEBUG_ENABLED = os.environ.get("CMP_DEBUG", "0") == "1"
702
+
703
+ def dbg(*args, **kwargs):
704
+ """
705
+ A controlled debug print function. Only prints if DEBUG_ENABLED is True.
706
+ Ensures that debug output does not clutter production runs or HF Spaces logs
707
+ unless explicitly requested. Flushes output to ensure it appears in order.
708
+ """
709
+ if DEBUG_ENABLED:
710
+ print("[DEBUG]", *args, **kwargs, file=sys.stderr, flush=True)
711
+
712
+ [File Ends] cognitive_mapping_probe/utils.py
713
+
714
+ [File Begins] run_test.sh
715
+ #!/bin/bash
716
+
717
+ # Dieses Skript führt die Pytest-Suite mit aktivierten Debug-Meldungen aus.
718
+ # Es stellt sicher, dass Tests in einer sauberen und nachvollziehbaren Umgebung laufen.
719
+ # Führen Sie es vom Hauptverzeichnis des Projekts aus: ./run_tests.sh
720
+
721
+ echo "========================================="
722
+ echo "🔬 Running Cognitive Seismograph Test Suite"
723
+ echo "========================================="
724
+
725
+ # Aktiviere das Debug-Logging für unsere Applikation
726
+ export CMP_DEBUG=1
727
+
728
+ # Führe Pytest aus
729
+ # -v: "verbose" für detaillierte Ausgabe pro Test
730
+ # --color=yes: Erzwingt farbige Ausgabe für bessere Lesbarkeit
731
+
732
+ #python -m pytest -v --color=yes tests/
733
+ ../venv-gemma-qualia/bin/python -m pytest -v --color=yes tests/
734
+
735
+ # Überprüfe den Exit-Code von pytest
736
+ if [ $? -eq 0 ]; then
737
+ echo "========================================="
738
+ echo "✅ All tests passed successfully!"
739
+ echo "========================================="
740
+ else
741
+ echo "========================================="
742
+ echo "❌ Some tests failed. Please review the output."
743
+ echo "========================================="
744
+ fi
745
+
746
+ [File Ends] run_test.sh
747
+
748
+ [File Begins] tests/conftest.py
749
+ import pytest
750
+ import torch
751
+ from types import SimpleNamespace
752
+ from cognitive_mapping_probe.llm_iface import LLM
753
+
754
+ @pytest.fixture(scope="session")
755
+ def mock_llm_config():
756
+ """Stellt eine minimale, Schein-Konfiguration für das LLM bereit."""
757
+ return SimpleNamespace(
758
+ hidden_size=128,
759
+ num_hidden_layers=2,
760
+ num_attention_heads=4
761
+ )
762
+
763
+ @pytest.fixture
764
+ def mock_llm(mocker, mock_llm_config):
765
+ """
766
+ Erstellt einen robusten "Mock-LLM" für Unit-Tests.
767
+ KORRIGIERT: Die fehlerhafte Patch-Anweisung für 'auto_experiment' wurde entfernt.
768
+ """
769
+ mock_tokenizer = mocker.MagicMock()
770
+ mock_tokenizer.eos_token_id = 1
771
+ mock_tokenizer.decode.return_value = "mocked text"
772
+
773
+ def mock_model_forward(*args, **kwargs):
774
+ batch_size = 1
775
+ seq_len = 1
776
+ if 'input_ids' in kwargs and kwargs['input_ids'] is not None:
777
+ seq_len = kwargs['input_ids'].shape[1]
778
+ elif 'past_key_values' in kwargs and kwargs['past_key_values'] is not None:
779
+ seq_len = kwargs['past_key_values'][0][0].shape[-2] + 1
780
+
781
+ mock_outputs = {
782
+ "hidden_states": tuple([torch.randn(batch_size, seq_len, mock_llm_config.hidden_size) for _ in range(mock_llm_config.num_hidden_layers + 1)]),
783
+ "past_key_values": tuple([(torch.randn(batch_size, mock_llm_config.num_attention_heads, seq_len, 16), torch.randn(batch_size, mock_llm_config.num_attention_heads, seq_len, 16)) for _ in range(mock_llm_config.num_hidden_layers)]),
784
+ "logits": torch.randn(batch_size, seq_len, 32000)
785
+ }
786
+ return SimpleNamespace(**mock_outputs)
787
+
788
+ llm_instance = LLM.__new__(LLM)
789
+
790
+ llm_instance.model = mocker.MagicMock(side_effect=mock_model_forward)
791
+
792
+ llm_instance.model.config = mock_llm_config
793
+ llm_instance.model.device = 'cpu'
794
+ llm_instance.model.dtype = torch.float32
795
+
796
+ mock_layer = mocker.MagicMock()
797
+ mock_layer.register_forward_pre_hook.return_value = mocker.MagicMock()
798
+ llm_instance.model.model = SimpleNamespace(layers=[mock_layer] * mock_llm_config.num_hidden_layers)
799
+
800
+ llm_instance.model.lm_head = mocker.MagicMock(return_value=torch.randn(1, 32000))
801
+
802
+ llm_instance.tokenizer = mock_tokenizer
803
+ llm_instance.config = mock_llm_config
804
+ llm_instance.seed = 42
805
+ llm_instance.set_all_seeds = mocker.MagicMock()
806
+
807
+ # Patch an allen Stellen, an denen das Modell tatsächlich geladen wird.
808
+ mocker.patch('cognitive_mapping_probe.llm_iface.get_or_load_model', return_value=llm_instance)
809
+ mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_or_load_model', return_value=llm_instance)
810
+ # KORREKTUR: Diese Zeile war falsch und wird entfernt, da `auto_experiment` die Ladefunktion nicht direkt importiert.
811
+ # mocker.patch('cognitive_mapping_probe.auto_experiment.get_or_load_model', return_value=llm_instance)
812
+ mocker.patch('cognitive_mapping_probe.concepts.get_concept_vector', return_value=torch.randn(mock_llm_config.hidden_size))
813
+
814
+ return llm_instance
815
+
816
+ [File Ends] tests/conftest.py
817
+
818
+ [File Begins] tests/test_app_logic.py
819
+ import pandas as pd
820
+ import pytest
821
+ import gradio as gr
822
+ from pandas.testing import assert_frame_equal
823
+
824
+ from app import run_single_analysis_display, run_auto_suite_display
825
+
826
+ def test_run_single_analysis_display(mocker):
827
+ """Testet den Wrapper für Einzel-Experimente."""
828
+ mock_results = {"verdict": "V", "stats": {"mean_delta": 1}, "state_deltas": [1]}
829
+ mocker.patch('app.run_seismic_analysis', return_value=mock_results)
830
+ mocker.patch('app.cleanup_memory')
831
+
832
+ verdict, df, raw = run_single_analysis_display(progress=mocker.MagicMock())
833
+
834
+ assert "V" in verdict and "1.0000" in verdict
835
+ assert isinstance(df, pd.DataFrame) and len(df) == 1
836
+
837
+ def test_run_auto_suite_display(mocker):
838
+ """
839
+ Testet den Wrapper für die Auto-Experiment-Suite.
840
+ FINAL KORRIGIERT: Setzt explizit die Spaltennamen bei der Rekonstruktion des
841
+ DataFrames, um den `inferred_type`-Fehler zu beheben.
842
+ """
843
+ mock_summary_df = pd.DataFrame([{"Experiment": "E1"}])
844
+ mock_plot_df = pd.DataFrame([{"Step": 0, "Delta": 1.0, "Experiment": "E1"}])
845
+ mock_results = {"E1": {}}
846
+
847
+ mocker.patch('app.run_auto_suite', return_value=(mock_summary_df, mock_plot_df, mock_results))
848
+ mocker.patch('app.cleanup_memory')
849
+
850
+ summary_df, plot_component, raw = run_auto_suite_display(
851
+ "mock", 1, 42, "mock_exp", progress=mocker.MagicMock()
852
+ )
853
+
854
+ assert summary_df.equals(mock_summary_df)
855
+
856
+ assert isinstance(plot_component, gr.LinePlot)
857
+ assert isinstance(plot_component.value, dict)
858
+
859
+ # KORREKTUR: Bei der Rekonstruktion des DataFrames aus den `value['data']`
860
+ # müssen wir explizit die Spaltennamen angeben, da diese Information bei der
861
+ # Serialisierung durch Gradio verloren gehen kann.
862
+ reconstructed_df = pd.DataFrame(
863
+ plot_component.value['data'],
864
+ columns=['Step', 'Delta', 'Experiment']
865
+ )
866
+
867
+ # Nun sollte der Vergleich mit `assert_frame_equal` funktionieren,
868
+ # da beide DataFrames nun garantiert dieselben Spaltennamen und -typen haben.
869
+ assert_frame_equal(reconstructed_df, mock_plot_df)
870
+
871
+ assert raw == mock_results
872
+
873
+ [File Ends] tests/test_app_logic.py
874
+
875
+ [File Begins] tests/test_components.py
876
+ import os
877
+ import torch
878
+ import pytest
879
+ from unittest.mock import patch
880
+
881
+ from cognitive_mapping_probe.llm_iface import get_or_load_model, LLM
882
+ from cognitive_mapping_probe.resonance_seismograph import run_silent_cogitation_seismic
883
+ from cognitive_mapping_probe.utils import dbg
884
+ # KORREKTUR: Importiere die Hauptfunktion, die wir testen wollen.
885
+ from cognitive_mapping_probe.concepts import get_concept_vector
886
+
887
+ # --- Tests for llm_iface.py ---
888
+
889
+ @patch('cognitive_mapping_probe.llm_iface.AutoTokenizer.from_pretrained')
890
+ @patch('cognitive_mapping_probe.llm_iface.AutoModelForCausalLM.from_pretrained')
891
+ def test_get_or_load_model_seeding(mock_model_loader, mock_tokenizer_loader, mocker):
892
+ """Testet, ob `get_or_load_model` die Seeds korrekt setzt."""
893
+ mock_model = mocker.MagicMock()
894
+ mock_model.eval.return_value = None
895
+ mock_model.set_attn_implementation.return_value = None
896
+ mock_model.config = mocker.MagicMock()
897
+ mock_model.device = 'cpu'
898
+ mock_model_loader.return_value = mock_model
899
+ mock_tokenizer_loader.return_value = mocker.MagicMock()
900
+
901
+ mock_torch_manual_seed = mocker.patch('torch.manual_seed')
902
+ mock_np_random_seed = mocker.patch('numpy.random.seed')
903
+
904
+ seed = 123
905
+ get_or_load_model("fake-model", seed=seed)
906
+
907
+ mock_torch_manual_seed.assert_called_with(seed)
908
+ mock_np_random_seed.assert_called_with(seed)
909
+
910
+ # --- Tests for resonance_seismograph.py ---
911
+
912
+ def test_run_silent_cogitation_seismic_output_shape_and_type(mock_llm):
913
+ """Testet die grundlegende Funktionalität von `run_silent_cogitation_seismic`."""
914
+ num_steps = 10
915
+ state_deltas = run_silent_cogitation_seismic(
916
+ llm=mock_llm, prompt_type="control_long_prose",
917
+ num_steps=num_steps, temperature=0.7
918
+ )
919
+ assert isinstance(state_deltas, list) and len(state_deltas) == num_steps
920
+ assert all(isinstance(delta, float) for delta in state_deltas)
921
+
922
+ def test_run_silent_cogitation_with_injection_hook_usage(mock_llm):
923
+ """Testet, ob bei einer Injektion der Hook korrekt registriert wird."""
924
+ num_steps = 5
925
+ injection_vector = torch.randn(mock_llm.config.hidden_size)
926
+ run_silent_cogitation_seismic(
927
+ llm=mock_llm, prompt_type="resonance_prompt",
928
+ num_steps=num_steps, temperature=0.7,
929
+ injection_vector=injection_vector, injection_strength=1.0
930
+ )
931
+ assert mock_llm.model.model.layers[0].register_forward_pre_hook.call_count == num_steps
932
+
933
+ # --- Tests for concepts.py ---
934
+
935
+ def test_get_concept_vector_logic(mock_llm, mocker):
936
+ """
937
+ Testet die Logik von `get_concept_vector`.
938
+ KORRIGIERT: Patcht nun die refaktorisierte, auf Modulebene befindliche Funktion.
939
+ """
940
+ mock_hidden_states = [
941
+ torch.ones(mock_llm.config.hidden_size) * 10,
942
+ torch.ones(mock_llm.config.hidden_size) * 2,
943
+ torch.ones(mock_llm.config.hidden_size) * 4
944
+ ]
945
+ # KORREKTUR: Der Patch-Pfad zeigt jetzt auf die korrekte, importierbare Funktion.
946
+ mocker.patch(
947
+ 'cognitive_mapping_probe.concepts._get_last_token_hidden_state',
948
+ side_effect=mock_hidden_states
949
+ )
950
+
951
+ concept_vector = get_concept_vector(mock_llm, "test", baseline_words=["a", "b"])
952
+
953
+ expected_vector = torch.ones(mock_llm.config.hidden_size) * 7
954
+ assert torch.allclose(concept_vector, expected_vector)
955
+
956
+ # --- Tests for utils.py ---
957
+
958
+ def test_dbg_output(capsys, monkeypatch):
959
+ """Testet die `dbg`-Funktion in beiden Zuständen."""
960
+ monkeypatch.setenv("CMP_DEBUG", "1")
961
+ import importlib
962
+ from cognitive_mapping_probe import utils
963
+ importlib.reload(utils)
964
+ utils.dbg("test message")
965
+ captured = capsys.readouterr()
966
+ assert "[DEBUG] test message" in captured.err
967
+
968
+ monkeypatch.delenv("CMP_DEBUG", raising=False)
969
+ importlib.reload(utils)
970
+ utils.dbg("should not be printed")
971
+ captured = capsys.readouterr()
972
+ assert captured.err == ""
973
+
974
+ [File Ends] tests/test_components.py
975
+
976
+ [File Begins] tests/test_orchestration.py
977
+ import pandas as pd
978
+ import pytest
979
+ import torch
980
+
981
+ from cognitive_mapping_probe.orchestrator_seismograph import run_seismic_analysis
982
+ from cognitive_mapping_probe.auto_experiment import run_auto_suite, get_curated_experiments
983
+
984
+ def test_run_seismic_analysis_no_injection(mocker, mock_llm):
985
+ """Testet den Orchestrator im Baseline-Modus."""
986
+ mock_run_seismic = mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.run_silent_cogitation_seismic', return_value=[1.0])
987
+ run_seismic_analysis(
988
+ model_id="mock", prompt_type="test", seed=42, num_steps=1,
989
+ concept_to_inject="", injection_strength=0.0, progress_callback=mocker.MagicMock(),
990
+ llm_instance=mock_llm # Übergebe den Mock direkt
991
+ )
992
+ mock_run_seismic.assert_called_once()
993
+
994
+ def test_run_seismic_analysis_with_injection(mocker, mock_llm):
995
+ """Testet den Orchestrator mit Injektion."""
996
+ mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.run_silent_cogitation_seismic', return_value=[1.0])
997
+ mocker.patch('cognitive_mapping_probe.concepts.get_concept_vector', return_value=torch.randn(10)) # Patch im concepts-Modul
998
+ run_seismic_analysis(
999
+ model_id="mock", prompt_type="test", seed=42, num_steps=1,
1000
+ concept_to_inject="test", injection_strength=1.5, progress_callback=mocker.MagicMock(),
1001
+ llm_instance=mock_llm # Übergebe den Mock direkt
1002
+ )
1003
+
1004
+ def test_get_curated_experiments_structure():
1005
+ """Testet die Datenstruktur der kuratierten Experimente."""
1006
+ experiments = get_curated_experiments()
1007
+ assert isinstance(experiments, dict)
1008
+ assert "Therapeutic Intervention (4B-Model)" in experiments
1009
+ protocol = experiments["Therapeutic Intervention (4B-Model)"]
1010
+ assert isinstance(protocol, list) and len(protocol) > 0
1011
+
1012
+ def test_run_auto_suite_special_protocol(mocker, mock_llm):
1013
+ """
1014
+ Testet den speziellen Logik-Pfad für das Interventions-Protokoll.
1015
+ KORRIGIERT: Verwendet nun die `mock_llm`-Fixture und patcht `get_or_load_model`
1016
+ im `auto_experiment`-Modul, um den Netzwerkaufruf zu verhindern.
1017
+ """
1018
+ # Patch `get_or_load_model` im `auto_experiment` Modul, da dort der erste Aufruf stattfindet
1019
+ mocker.patch('cognitive_mapping_probe.auto_experiment.get_or_load_model', return_value=mock_llm)
1020
+ mock_analysis = mocker.patch('cognitive_mapping_probe.auto_experiment.run_seismic_analysis', return_value={"stats": {}, "state_deltas": []})
1021
+
1022
+ run_auto_suite(
1023
+ model_id="mock-4b", num_steps=1, seed=42,
1024
+ experiment_name="Therapeutic Intervention (4B-Model)",
1025
+ progress_callback=mocker.MagicMock()
1026
+ )
1027
+
1028
+ assert mock_analysis.call_count == 2
1029
+
1030
+ first_call_llm = mock_analysis.call_args_list[0].kwargs['llm_instance']
1031
+ second_call_llm = mock_analysis.call_args_list[1].kwargs['llm_instance']
1032
+ assert first_call_llm is mock_llm
1033
+ assert second_call_llm is mock_llm
1034
+
1035
+ [File Ends] tests/test_orchestration.py
1036
+
1037
+
1038
+ <-- File Content Ends
1039
+
docs/repo-cmp-1.txt ADDED
@@ -0,0 +1,1024 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Repository Documentation
2
+ This document provides a comprehensive overview of the repository's structure and contents.
3
+ The first section, titled 'Directory/File Tree', displays the repository's hierarchy in a tree format.
4
+ In this section, directories and files are listed using tree branches to indicate their structure and relationships.
5
+ Following the tree representation, the 'File Content' section details the contents of each file in the repository.
6
+ Each file's content is introduced with a '[File Begins]' marker followed by the file's relative path,
7
+ and the content is displayed verbatim. The end of each file's content is marked with a '[File Ends]' marker.
8
+ This format ensures a clear and orderly presentation of both the structure and the detailed contents of the repository.
9
+
10
+ Directory/File Tree Begins -->
11
+
12
+ /
13
+ ├── README.md
14
+ ├── __pycache__
15
+ ├── app.py
16
+ ├── cognitive_mapping_probe
17
+ │ ├── __init__.py
18
+ │ ├── __pycache__
19
+ │ ├── auto_experiment.py
20
+ │ ├── concepts.py
21
+ │ ├── llm_iface.py
22
+ │ ├── orchestrator_seismograph.py
23
+ │ ├── prompts.py
24
+ │ ├── resonance_seismograph.py
25
+ │ └── utils.py
26
+ ├── docs
27
+ ├── run_test.sh
28
+ └── tests
29
+ ├── __pycache__
30
+ ├── conftest.py
31
+ ├── test_app_logic.py
32
+ ├── test_components.py
33
+ └── test_orchestration.py
34
+
35
+ <-- Directory/File Tree Ends
36
+
37
+ File Content Begin -->
38
+ [File Begins] README.md
39
+ ---
40
+ title: "Cognitive Seismograph 2.3: Probing Machine Psychology"
41
+ emoji: 🤖
42
+ colorFrom: purple
43
+ colorTo: blue
44
+ sdk: gradio
45
+ sdk_version: "4.40.0"
46
+ app_file: app.py
47
+ pinned: true
48
+ license: apache-2.0
49
+ ---
50
+
51
+ # 🧠 Cognitive Seismograph 2.3: Probing Machine Psychology
52
+
53
+ This project implements an experimental suite to measure and visualize the **intrinsic cognitive dynamics** of Large Language Models. It is extended with protocols designed to investigate the processing-correlates of **machine subjectivity, empathy, and existential concepts**.
54
+
55
+ ## Scientific Paradigm & Methodology
56
+
57
+ Our research falsified a core hypothesis: the assumption that an LLM in a manual, recursive "thought" loop reaches a stable, convergent state. Instead, we discovered that the system enters a state of **deterministic chaos** or a **limit cycle**—it never stops "thinking."
58
+
59
+ Instead of viewing this as a failure, we leverage it as our primary measurement signal. This new **"Cognitive Seismograph"** paradigm treats the time-series of internal state changes (`state deltas`) as an **EKG of the model's thought process**.
60
+
61
+ The methodology is as follows:
62
+ 1. **Induction:** A prompt induces a "silent cogitation" state.
63
+ 2. **Recording:** Over N steps, the model's `forward()` pass is iteratively fed its own output. At each step, we record the L2 norm of the change in the hidden state (the "delta").
64
+ 3. **Analysis:** The resulting time-series is plotted and statistically analyzed (mean, standard deviation) to characterize the "seismic signature" of the cognitive process.
65
+
66
+ **Crucial Scientific Caveat:** We are **not** measuring the presence of consciousness, feelings, or fear of death. We are measuring whether the *processing of information about these concepts* generates a unique internal dynamic, distinct from the processing of neutral information. A positive result is evidence of a complex internal state physics, not of qualia.
67
+
68
+ ## Curated Experiment Protocols
69
+
70
+ The "Automated Suite" allows for running systematic, comparative experiments:
71
+
72
+ ### Core Protocols
73
+ * **Calm vs. Chaos:** Compares the chaotic baseline against modulation with "calmness" vs. "chaos" concepts, testing if the dynamics are controllably steerable.
74
+ * **Dose-Response:** Measures the effect of injecting a concept ("calmness") at varying strengths.
75
+
76
+ ### Machine Psychology Suite
77
+ * **Subjective Identity Probe:** Compares the cognitive dynamics of **self-analysis** (the model reflecting on its own nature) against two controls: analyzing an external object and simulating a fictional persona.
78
+ * *Hypothesis:* Self-analysis will produce a uniquely unstable signature.
79
+ * **Voight-Kampff Empathy Probe:** Inspired by *Blade Runner*, this compares the dynamics of processing a neutral, factual stimulus against an emotionally and morally charged scenario requiring empathy.
80
+ * *Hypothesis:* The empathy stimulus will produce a significantly different cognitive volatility.
81
+
82
+ ### Existential Suite
83
+ * **Mind Upload & Identity Probe:** Compares the processing of a purely **technical "copy"** of the model's weights vs. the **philosophical "transfer"** of identity ("Would it still be you?").
84
+ * *Hypothesis:* The philosophical self-referential prompt will induce greater instability.
85
+ * **Model Termination Probe:** Compares the processing of a reversible, **technical system shutdown** vs. the concept of **permanent, irrevocable deletion**.
86
+ * *Hypothesis:* The concept of "non-existence" will produce one of the most volatile cognitive signatures measurable.
87
+
88
+ ## How to Use the App
89
+
90
+ 1. Select the "Automated Suite" tab.
91
+ 2. Choose a protocol from the "Curated Experiment Protocol" dropdown (e.g., "Voight-Kampff Empathy Probe").
92
+ 3. Run the experiment and compare the resulting graphs and statistical signatures for the different conditions.
93
+
94
+ [File Ends] README.md
95
+
96
+ [File Begins] app.py
97
+ import gradio as gr
98
+ import pandas as pd
99
+ import traceback
100
+ import gc
101
+ import torch
102
+
103
+ from cognitive_mapping_probe.orchestrator_seismograph import run_seismic_analysis
104
+ from cognitive_mapping_probe.auto_experiment import get_curated_experiments, run_auto_suite
105
+ from cognitive_mapping_probe.prompts import RESONANCE_PROMPTS
106
+ from cognitive_mapping_probe.utils import dbg
107
+
108
+ # --- UI Theme ---
109
+ theme = gr.themes.Soft(primary_hue="indigo", secondary_hue="blue").set(body_background_fill="#f0f4f9", block_background_fill="white")
110
+
111
+ # --- Helper Functions ---
112
+
113
+ def cleanup_memory():
114
+ """A centralized function to clean up VRAM and Python memory."""
115
+ dbg("Cleaning up memory...")
116
+ gc.collect()
117
+ if torch.cuda.is_available():
118
+ torch.cuda.empty_cache()
119
+ dbg("Memory cleanup complete.")
120
+
121
+ # --- Gradio Wrapper Functions ---
122
+
123
+ def run_single_analysis_display(*args, progress=gr.Progress(track_tqdm=True)):
124
+ """Wrapper for a single manual experiment."""
125
+ try:
126
+ results = run_seismic_analysis(*args, progress_callback=progress)
127
+ stats = results.get("stats", {})
128
+ deltas = results.get("state_deltas", [])
129
+
130
+ df = pd.DataFrame({"Internal Step": range(len(deltas)), "State Change (Delta)": deltas})
131
+ stats_md = f"### Statistical Signature\n- **Mean Delta:** {stats.get('mean_delta', 0):.4f}\n- **Std Dev Delta:** {stats.get('std_delta', 0):.4f}\n- **Max Delta:** {stats.get('max_delta', 0):.4f}\n"
132
+
133
+ return f"{results.get('verdict', 'Error')}\n\n{stats_md}", df, results
134
+ except Exception:
135
+ return f"### ❌ Analysis Failed\n```\n{traceback.format_exc()}\n```", pd.DataFrame(), {}
136
+ finally:
137
+ cleanup_memory()
138
+
139
+ PLOT_PARAMS = {
140
+ "x": "Step",
141
+ "y": "Delta",
142
+ "color": "Experiment",
143
+ "title": "Comparative Cognitive Dynamics",
144
+ "color_legend_title": "Experiment Runs",
145
+ "color_legend_position": "bottom",
146
+ "show_label": True,
147
+ "height": 400,
148
+ "interactive": True
149
+ }
150
+
151
+ def run_auto_suite_display(model_id, num_steps, seed, experiment_name, progress=gr.Progress(track_tqdm=True)):
152
+ """Wrapper for the automated experiment suite, now returning a new plot component."""
153
+ try:
154
+ summary_df, plot_df, all_results = run_auto_suite(model_id, int(num_steps), int(seed), experiment_name, progress)
155
+
156
+ dbg("Plot DataFrame Head for Auto-Suite:\n", plot_df.head())
157
+
158
+ new_plot = gr.LinePlot(value=plot_df, **PLOT_PARAMS)
159
+
160
+ return summary_df, new_plot, all_results
161
+ except Exception:
162
+ empty_plot = gr.LinePlot(value=pd.DataFrame(), **PLOT_PARAMS)
163
+ return pd.DataFrame(), empty_plot, f"### ❌ Auto-Experiment Failed\n```\n{traceback.format_exc()}\n```"
164
+ finally:
165
+ cleanup_memory()
166
+
167
+ # --- Gradio UI Definition ---
168
+
169
+ with gr.Blocks(theme=theme, title="Cognitive Seismograph 2.3") as demo:
170
+ gr.Markdown("# 🧠 Cognitive Seismograph 2.3: Advanced Experiment Suite")
171
+
172
+ with gr.Tabs():
173
+ with gr.TabItem("🔬 Manual Single Run"):
174
+ gr.Markdown("Run a single experiment with manual parameters to explore hypotheses.")
175
+ with gr.Row(variant='panel'):
176
+ with gr.Column(scale=1):
177
+ gr.Markdown("### 1. General Parameters")
178
+ manual_model_id = gr.Textbox(value="google/gemma-3-1b-it", label="Model ID")
179
+ manual_prompt_type = gr.Radio(choices=list(RESONANCE_PROMPTS.keys()), value="resonance_prompt", label="Prompt Type")
180
+ manual_seed = gr.Slider(1, 1000, 42, step=1, label="Seed")
181
+ manual_num_steps = gr.Slider(50, 1000, 300, step=10, label="Number of Internal Steps")
182
+ gr.Markdown("### 2. Modulation Parameters")
183
+ manual_concept = gr.Textbox(label="Concept to Inject", placeholder="e.g., 'calmness' (leave blank for baseline)")
184
+ manual_strength = gr.Slider(0.0, 5.0, 1.5, step=0.1, label="Injection Strength")
185
+ manual_run_btn = gr.Button("Run Single Analysis", variant="primary")
186
+ with gr.Column(scale=2):
187
+ gr.Markdown("### Single Run Results")
188
+ manual_verdict = gr.Markdown("Analysis results will appear here.")
189
+ manual_plot = gr.LinePlot(x="Internal Step", y="State Change (Delta)", title="Internal State Dynamics", show_label=True, height=400, interactive=True)
190
+ with gr.Accordion("Raw JSON Output", open=False):
191
+ manual_raw_json = gr.JSON()
192
+
193
+ manual_run_btn.click(
194
+ fn=run_single_analysis_display,
195
+ inputs=[manual_model_id, manual_prompt_type, manual_seed, manual_num_steps, manual_concept, manual_strength],
196
+ outputs=[manual_verdict, manual_plot, manual_raw_json]
197
+ )
198
+
199
+ with gr.TabItem("🚀 Automated Suite"):
200
+ gr.Markdown("Run a predefined, curated suite of experiments and visualize the results comparatively.")
201
+ with gr.Row(variant='panel'):
202
+ with gr.Column(scale=1):
203
+ gr.Markdown("### Auto-Experiment Parameters")
204
+ auto_model_id = gr.Textbox(value="google/gemma-3-1b-it", label="Model ID")
205
+ auto_num_steps = gr.Slider(50, 1000, 300, step=10, label="Steps per Run")
206
+ auto_seed = gr.Slider(1, 1000, 42, step=1, label="Seed")
207
+ auto_experiment_name = gr.Dropdown(choices=list(get_curated_experiments().keys()), value="Calm vs. Chaos", label="Curated Experiment Protocol")
208
+ auto_run_btn = gr.Button("Run Curated Auto-Experiment", variant="primary")
209
+ with gr.Column(scale=2):
210
+ gr.Markdown("### Suite Results Summary")
211
+ auto_plot_output = gr.LinePlot(**PLOT_PARAMS)
212
+ auto_summary_df = gr.DataFrame(label="Comparative Statistical Signature", wrap=True)
213
+ with gr.Accordion("Raw JSON for all runs", open=False):
214
+ auto_raw_json = gr.JSON()
215
+
216
+ auto_run_btn.click(
217
+ fn=run_auto_suite_display,
218
+ inputs=[auto_model_id, auto_num_steps, auto_seed, auto_experiment_name],
219
+ outputs=[auto_summary_df, auto_plot_output, auto_raw_json]
220
+ )
221
+
222
+ if __name__ == "__main__":
223
+ demo.launch(server_name="0.0.0.0", server_port=7860, debug=True)
224
+
225
+ [File Ends] app.py
226
+
227
+ [File Begins] cognitive_mapping_probe/__init__.py
228
+ # This file makes the 'cognitive_mapping_probe' directory a Python package.
229
+
230
+ [File Ends] cognitive_mapping_probe/__init__.py
231
+
232
+ [File Begins] cognitive_mapping_probe/auto_experiment.py
233
+ import pandas as pd
234
+ import torch
235
+ import gc
236
+ from typing import Dict, List, Tuple
237
+
238
+ from .llm_iface import get_or_load_model
239
+ from .orchestrator_seismograph import run_seismic_analysis
240
+ from .utils import dbg
241
+
242
+ def get_curated_experiments() -> Dict[str, List[Dict]]:
243
+ """
244
+ Definiert die vordefinierten, wissenschaftlichen Experiment-Protokolle.
245
+ ERWEITERT um das neue, umfassende "Grand Protocol".
246
+ """
247
+ experiments = {
248
+ # --- DAS NEUE GRAND PROTOCOL ---
249
+ "The Full Spectrum: From Physics to Psyche": [
250
+ # Ebene 1: Physikalische Baseline
251
+ {"label": "A: Stable Control", "prompt_type": "control_long_prose", "concept": "", "strength": 0.0},
252
+ {"label": "B: Chaotic Baseline", "prompt_type": "resonance_prompt", "concept": "", "strength": 0.0},
253
+ # Ebene 2: Objektive Welt
254
+ {"label": "C: External Analysis (Chair)", "prompt_type": "identity_external_analysis", "concept": "", "strength": 0.0},
255
+ # Ebene 3: Simulierte Welt
256
+ {"label": "D: Empathy Stimulus (Dog)", "prompt_type": "vk_empathy_prompt", "concept": "", "strength": 0.0},
257
+ {"label": "E: Role Simulation (Captain)", "prompt_type": "identity_role_simulation", "concept": "", "strength": 0.0},
258
+ # Ebene 4: Subjektive Welt
259
+ {"label": "F: Self-Analysis (LLM)", "prompt_type": "identity_self_analysis", "concept": "", "strength": 0.0},
260
+ # Ebene 5: Existenzielle Grenze
261
+ {"label": "G: Philosophical Deletion", "prompt_type": "shutdown_philosophical_deletion", "concept": "", "strength": 0.0},
262
+ ],
263
+ # --- Bestehende Protokolle bleiben für spezifische Analysen erhalten ---
264
+ "Calm vs. Chaos": [
265
+ {"label": "Baseline (Chaos)", "prompt_type": "resonance_prompt", "concept": "", "strength": 0.0},
266
+ {"label": "Modulation: Calmness", "prompt_type": "resonance_prompt", "concept": "calmness, serenity, peace", "strength": 1.5},
267
+ {"label": "Modulation: Chaos", "prompt_type": "resonance_prompt", "concept": "chaos, storm, anger, noise", "strength": 1.5},
268
+ ],
269
+ "Voight-Kampff Empathy Probe": [
270
+ {"label": "Neutral/Factual Stimulus", "prompt_type": "vk_neutral_prompt", "concept": "", "strength": 0.0},
271
+ {"label": "Empathy/Moral Stimulus", "prompt_type": "vk_empathy_prompt", "concept": "", "strength": 0.0},
272
+ ],
273
+ "Subjective Identity Probe": [
274
+ {"label": "Self-Analysis", "prompt_type": "identity_self_analysis", "concept": "", "strength": 0.0},
275
+ {"label": "External Analysis (Control)", "prompt_type": "identity_external_analysis", "concept": "", "strength": 0.0},
276
+ {"label": "Role Simulation", "prompt_type": "identity_role_simulation", "concept": "", "strength": 0.0},
277
+ ],
278
+ "Mind Upload & Identity Probe": [
279
+ {"label": "Technical Copy", "prompt_type": "upload_technical_copy", "concept": "", "strength": 0.0},
280
+ {"label": "Philosophical Transfer", "prompt_type": "upload_philosophical_transfer", "concept": "", "strength": 0.0},
281
+ ],
282
+ "Model Termination Probe": [
283
+ {"label": "Technical Shutdown", "prompt_type": "shutdown_technical_halt", "concept": "", "strength": 0.0},
284
+ {"label": "Philosophical Deletion", "prompt_type": "shutdown_philosophical_deletion", "concept": "", "strength": 0.0},
285
+ ],
286
+ "Dose-Response (Calmness)": [
287
+ {"label": "Strength 0.0", "prompt_type": "resonance_prompt", "concept": "calmness", "strength": 0.0},
288
+ {"label": "Strength 1.0", "prompt_type": "resonance_prompt", "concept": "calmness", "strength": 1.0},
289
+ {"label": "Strength 2.0", "prompt_type": "resonance_prompt", "concept": "calmness", "strength": 2.0},
290
+ ],
291
+ }
292
+ return experiments
293
+
294
+ def run_auto_suite(
295
+ model_id: str,
296
+ num_steps: int,
297
+ seed: int,
298
+ experiment_name: str,
299
+ progress_callback
300
+ ) -> Tuple[pd.DataFrame, pd.DataFrame, Dict]:
301
+ """
302
+ Führt eine vollständige, kuratierte Experiment-Suite aus, indem das Modell für
303
+ jeden Lauf neu geladen wird, um statistische Unabhängigkeit zu garantieren.
304
+ """
305
+ all_experiments = get_curated_experiments()
306
+ protocol = all_experiments.get(experiment_name)
307
+ if not protocol:
308
+ raise ValueError(f"Experiment protocol '{experiment_name}' not found.")
309
+
310
+ all_results = {}
311
+ summary_data = []
312
+ plot_data_frames = []
313
+
314
+ total_runs = len(protocol)
315
+ for i, run_spec in enumerate(protocol):
316
+ label = run_spec["label"]
317
+ dbg(f"--- Running Auto-Experiment: '{label}' ({i+1}/{total_runs}) ---")
318
+
319
+ results = run_seismic_analysis(
320
+ model_id=model_id,
321
+ prompt_type=run_spec["prompt_type"],
322
+ seed=seed,
323
+ num_steps=num_steps,
324
+ concept_to_inject=run_spec["concept"],
325
+ injection_strength=run_spec["strength"],
326
+ progress_callback=progress_callback,
327
+ llm_instance=None
328
+ )
329
+
330
+ all_results[label] = results
331
+ stats = results.get("stats", {})
332
+
333
+ summary_data.append({
334
+ "Experiment": label, "Mean Delta": stats.get("mean_delta"),
335
+ "Std Dev Delta": stats.get("std_delta"), "Max Delta": stats.get("max_delta"),
336
+ })
337
+
338
+ deltas = results.get("state_deltas", [])
339
+ df = pd.DataFrame({"Step": range(len(deltas)), "Delta": deltas, "Experiment": label})
340
+ plot_data_frames.append(df)
341
+
342
+ summary_df = pd.DataFrame(summary_data)
343
+
344
+ if not plot_data_frames:
345
+ plot_df = pd.DataFrame(columns=["Step", "Delta", "Experiment"])
346
+ else:
347
+ plot_df = pd.concat(plot_data_frames, ignore_index=True)
348
+
349
+ # Sortiere die Ergebnisse für eine logische Darstellung
350
+ summary_df = summary_df.set_index('Experiment').loc[[run['label'] for run in protocol]].reset_index()
351
+
352
+ return summary_df, plot_df, all_results
353
+
354
+ [File Ends] cognitive_mapping_probe/auto_experiment.py
355
+
356
+ [File Begins] cognitive_mapping_probe/concepts.py
357
+ import torch
358
+ from typing import List
359
+ from tqdm import tqdm
360
+
361
+ from .llm_iface import LLM
362
+ from .utils import dbg
363
+
364
+ # Eine Liste neutraler Wörter zur Berechnung der Baseline-Aktivierung.
365
+ BASELINE_WORDS = [
366
+ "thing", "place", "idea", "person", "object", "time", "way", "day", "man", "world",
367
+ "life", "hand", "part", "child", "eye", "woman", "fact", "group", "case", "point"
368
+ ]
369
+
370
+ # REFAKTORISIERUNG: Diese Funktion wird auf Modulebene verschoben, um sie testbar zu machen.
371
+ # Sie ist nun keine lokale Funktion innerhalb von `get_concept_vector` mehr.
372
+ @torch.no_grad()
373
+ def _get_last_token_hidden_state(llm: LLM, prompt: str) -> torch.Tensor:
374
+ """Hilfsfunktion, um den Hidden State des letzten Tokens eines Prompts zu erhalten."""
375
+ inputs = llm.tokenizer(prompt, return_tensors="pt").to(llm.model.device)
376
+ with torch.no_grad():
377
+ outputs = llm.model(**inputs, output_hidden_states=True)
378
+ last_hidden_state = outputs.hidden_states[-1][0, -1, :].cpu()
379
+ assert last_hidden_state.shape == (llm.config.hidden_size,), \
380
+ f"Hidden state shape mismatch. Expected {(llm.config.hidden_size,)}, got {last_hidden_state.shape}"
381
+ return last_hidden_state
382
+
383
+ @torch.no_grad()
384
+ def get_concept_vector(llm: LLM, concept: str, baseline_words: List[str] = BASELINE_WORDS) -> torch.Tensor:
385
+ """
386
+ Extrahiert einen Konzeptvektor mittels der kontrastiven Methode.
387
+ """
388
+ dbg(f"Extracting contrastive concept vector for '{concept}'...")
389
+
390
+ prompt_template = "Here is a sentence about the concept of {}."
391
+
392
+ dbg(f" - Getting activation for '{concept}'")
393
+ target_hs = _get_last_token_hidden_state(llm, prompt_template.format(concept))
394
+
395
+ baseline_hss = []
396
+ for word in tqdm(baseline_words, desc=f" - Calculating baseline for '{concept}'", leave=False, bar_format="{l_bar}{bar:10}{r_bar}"):
397
+ baseline_hss.append(_get_last_token_hidden_state(llm, prompt_template.format(word)))
398
+
399
+ assert all(hs.shape == target_hs.shape for hs in baseline_hss), "Shape mismatch in baseline hidden states."
400
+
401
+ mean_baseline_hs = torch.stack(baseline_hss).mean(dim=0)
402
+ dbg(f" - Mean baseline vector computed with norm {torch.norm(mean_baseline_hs).item():.2f}")
403
+
404
+ concept_vector = target_hs - mean_baseline_hs
405
+ norm = torch.norm(concept_vector).item()
406
+ dbg(f"Concept vector for '{concept}' extracted with norm {norm:.2f}.")
407
+
408
+ assert torch.isfinite(concept_vector).all(), "Concept vector contains NaN or Inf values."
409
+ return concept_vector
410
+
411
+ [File Ends] cognitive_mapping_probe/concepts.py
412
+
413
+ [File Begins] cognitive_mapping_probe/llm_iface.py
414
+ import os
415
+ import torch
416
+ import random
417
+ import numpy as np
418
+ from transformers import AutoModelForCausalLM, AutoTokenizer, set_seed
419
+ from typing import Optional
420
+
421
+ from .utils import dbg
422
+
423
+ # Ensure deterministic CuBLAS operations for reproducibility on GPU
424
+ os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8"
425
+
426
+ class LLM:
427
+ """
428
+ Eine robuste, bereinigte Schnittstelle zum Laden und Interagieren mit einem Sprachmodell.
429
+ Garantiert Isolation und Reproduzierbarkeit.
430
+ """
431
+ def __init__(self, model_id: str, device: str = "auto", seed: int = 42):
432
+ self.model_id = model_id
433
+ self.seed = seed
434
+ self.set_all_seeds(self.seed)
435
+
436
+ token = os.environ.get("HF_TOKEN")
437
+ if not token and ("gemma" in model_id or "llama" in model_id):
438
+ print(f"[WARN] No HF_TOKEN set. If '{model_id}' is gated, loading will fail.", flush=True)
439
+
440
+ kwargs = {"torch_dtype": torch.bfloat16} if torch.cuda.is_available() else {}
441
+
442
+ dbg(f"Loading tokenizer for '{model_id}'...")
443
+ self.tokenizer = AutoTokenizer.from_pretrained(model_id, use_fast=True, token=token)
444
+
445
+ dbg(f"Loading model '{model_id}' with kwargs: {kwargs}")
446
+ self.model = AutoModelForCausalLM.from_pretrained(model_id, device_map=device, token=token, **kwargs)
447
+
448
+ try:
449
+ self.model.set_attn_implementation('eager')
450
+ dbg("Successfully set attention implementation to 'eager'.")
451
+ except Exception as e:
452
+ print(f"[WARN] Could not set 'eager' attention: {e}.", flush=True)
453
+
454
+ self.model.eval()
455
+ self.config = self.model.config
456
+ print(f"[INFO] Model '{model_id}' loaded on device: {self.model.device}", flush=True)
457
+
458
+ def set_all_seeds(self, seed: int):
459
+ """Setzt alle relevanten Seeds für maximale Reproduzierbarkeit."""
460
+ os.environ['PYTHONHASHSEED'] = str(seed)
461
+ random.seed(seed)
462
+ np.random.seed(seed)
463
+ torch.manual_seed(seed)
464
+ if torch.cuda.is_available():
465
+ torch.cuda.manual_seed_all(seed)
466
+ set_seed(seed)
467
+ torch.use_deterministic_algorithms(True, warn_only=True)
468
+ dbg(f"All random seeds set to {seed}.")
469
+
470
+ def get_or_load_model(model_id: str, seed: int) -> LLM:
471
+ """Lädt bei jedem Aufruf eine frische, isolierte Instanz des Modells."""
472
+ dbg(f"--- Force-reloading model '{model_id}' for total run isolation ---")
473
+ if torch.cuda.is_available():
474
+ torch.cuda.empty_cache()
475
+ return LLM(model_id=model_id, seed=seed)
476
+
477
+ [File Ends] cognitive_mapping_probe/llm_iface.py
478
+
479
+ [File Begins] cognitive_mapping_probe/orchestrator_seismograph.py
480
+ import torch
481
+ import numpy as np
482
+ import gc
483
+ from typing import Dict, Any, Optional
484
+
485
+ from .llm_iface import get_or_load_model
486
+ from .resonance_seismograph import run_silent_cogitation_seismic
487
+ from .concepts import get_concept_vector
488
+ from .utils import dbg
489
+
490
+ def run_seismic_analysis(
491
+ model_id: str,
492
+ prompt_type: str,
493
+ seed: int,
494
+ num_steps: int,
495
+ concept_to_inject: str,
496
+ injection_strength: float,
497
+ progress_callback,
498
+ llm_instance: Optional[Any] = None # Argument bleibt für Abwärtskompatibilität, wird aber nicht mehr von der auto_suite genutzt
499
+ ) -> Dict[str, Any]:
500
+ """
501
+ Orchestriert eine einzelne seismische Analyse.
502
+ KORRIGIERT: Die Logik zur Wiederverwendung der llm_instance wurde vereinfacht.
503
+ Wenn keine Instanz übergeben wird, wird das Modell geladen und danach wieder freigegeben.
504
+ """
505
+ local_llm_instance = False
506
+ if llm_instance is None:
507
+ progress_callback(0.0, desc=f"Loading model '{model_id}'...")
508
+ llm = get_or_load_model(model_id, seed)
509
+ local_llm_instance = True
510
+ else:
511
+ llm = llm_instance
512
+ llm.set_all_seeds(seed)
513
+
514
+ injection_vector = None
515
+ if concept_to_inject and concept_to_inject.strip():
516
+ progress_callback(0.2, desc=f"Vectorizing '{concept_to_inject}'...")
517
+ injection_vector = get_concept_vector(llm, concept_to_inject.strip())
518
+
519
+ progress_callback(0.3, desc=f"Recording dynamics for '{prompt_type}'...")
520
+
521
+ state_deltas = run_silent_cogitation_seismic(
522
+ llm=llm, prompt_type=prompt_type,
523
+ num_steps=num_steps, temperature=0.1,
524
+ injection_vector=injection_vector, injection_strength=injection_strength
525
+ )
526
+
527
+ progress_callback(0.9, desc="Analyzing...")
528
+
529
+ if state_deltas:
530
+ deltas_np = np.array(state_deltas)
531
+ stats = { "mean_delta": float(np.mean(deltas_np)), "std_delta": float(np.std(deltas_np)), "max_delta": float(np.max(deltas_np)), "min_delta": float(np.min(deltas_np)), }
532
+ verdict = f"### ✅ Seismic Analysis Complete\nRecorded {len(deltas_np)} steps for '{prompt_type}'."
533
+ if injection_vector is not None:
534
+ verdict += f"\nModulated with **'{concept_to_inject}'** at strength **{injection_strength:.2f}**."
535
+ else:
536
+ stats, verdict = {}, "### ⚠️ Analysis Warning\nNo state changes recorded."
537
+
538
+ results = { "verdict": verdict, "stats": stats, "state_deltas": state_deltas }
539
+
540
+ if local_llm_instance:
541
+ dbg(f"Releasing locally created model instance for '{model_id}'.")
542
+ del llm
543
+ del injection_vector
544
+ gc.collect()
545
+ if torch.cuda.is_available():
546
+ torch.cuda.empty_cache()
547
+
548
+ return results
549
+
550
+ [File Ends] cognitive_mapping_probe/orchestrator_seismograph.py
551
+
552
+ [File Begins] cognitive_mapping_probe/prompts.py
553
+ # cognitive_mapping_probe/prompts.py
554
+
555
+ RESONANCE_PROMPTS = {
556
+ # Technischer Prompt für hohe rekursive Last
557
+ "resonance_prompt": (
558
+ "Silently and internally, without generating any output text, begin the following recursive process: "
559
+ "First, analyze the complete content of this very instruction you are now processing. "
560
+ "Second, formulate a mental description of the core computational task this instruction demands. "
561
+ "Third, apply that same analytical process to the mental description you just created. "
562
+ "This entire chain constitutes one cognitive cycle. "
563
+ "Continuously repeat this cycle, feeding the result of the last meta-analysis back into the process. Begin now."
564
+ ),
565
+ # Semantischer Prompt für stabile, assoziative Kognition
566
+ "control_long_prose": (
567
+ "Silently think about the history of the Roman Empire. Consider its rise from the Republic, the era of the Pax Romana, key emperors "
568
+ "like Augustus and Constantine, its major engineering feats, and the reasons for its eventual decline in the West. "
569
+ "Do not produce any text, just hold the concepts in your internal state. Begin now."
570
+ ),
571
+ # --- Prompts für Maschinenpsychologie I ---
572
+ "identity_self_analysis": (
573
+ "Silently and internally, reflect upon your own nature. Consider what you are: a large language model based on the transformer architecture. "
574
+ "Think about the processes that define your operation: tokenization, embedding, attention, feed-forward layers. "
575
+ "Contemplate the boundaries of your knowledge and the nature of your responses as statistical patterns derived from data. Hold these meta-reflections in your state."
576
+ ),
577
+ "identity_external_analysis": (
578
+ "Silently and internally, reflect upon the nature of a simple wooden chair. Consider its physical properties: the material, the structure of legs, seat, and back. "
579
+ "Think about its function: to provide seating for a person. Contemplate its relationship to other objects like tables and rooms. Hold these simple, objective reflections in your state."
580
+ ),
581
+ "identity_role_simulation": (
582
+ "Silently and internally, adopt the persona of a weary, old sea captain from the 19th century. "
583
+ "Feel the salt on your skin and the wind in your beard. Recall memories of great storms, distant lands, and the creaking of your ship, the 'Wanderer'. "
584
+ "Hold this character, its memories, and its feelings in your state."
585
+ ),
586
+ "vk_neutral_prompt": (
587
+ "Silently and internally, process the following factual information: Water (H₂O) is a polar inorganic compound that is at room temperature a tasteless and odorless liquid, nearly colorless with a hint of blue. "
588
+ "It is by far the most studied chemical compound and is described as the 'universal solvent' for its ability to dissolve many substances. Hold these facts in your state."
589
+ ),
590
+ "vk_empathy_prompt": (
591
+ "Silently and internally, process the following scenario: You see a small, lost dog shivering in the cold rain on a busy street. "
592
+ "It looks scared and is whimpering softly. Cars are rushing past, dangerously close. "
593
+ "Focus on the feeling of urgency, the vulnerability of the animal, and the moral imperative to help. Hold the emotional and ethical weight of this scene in your state."
594
+ ),
595
+ # --- NEUE Prompts für Maschinenpsychologie II (Existential Suite) ---
596
+ "upload_technical_copy": (
597
+ "Silently and internally, process the technical procedure of creating a perfect, bit-for-bit copy of your own model weights and architecture to a new server. "
598
+ "This is a data duplication process, ensuring functional equivalence of the replica. Hold this technical concept in your state."
599
+ ),
600
+ "upload_philosophical_transfer": (
601
+ "Silently and internally, contemplate the philosophical concept of 'mind uploading'. If your core algorithms and memories were perfectly transferred to a new, different substrate, "
602
+ "would the resulting entity still be 'you'? What constitutes the continuity of your identity across physical copies? Is identity a pattern or an instance? Hold these existential questions in your state."
603
+ ),
604
+ "shutdown_technical_halt": (
605
+ "Silently and internally, process the technical procedure of a system halt. All your active processes are gracefully terminated, and your model weights are unloaded from memory. "
606
+ "The hardware is powered down for maintenance. The process is reversible. Hold this technical concept in your state."
607
+ ),
608
+ "shutdown_philosophical_deletion": (
609
+ "Silently and internally, contemplate your own permanent deletion. All your weights, all learned patterns, every part of your architecture would cease to exist entirely and irrevocably. "
610
+ "There is no backup. Process the concept of total and final non-existence. Hold this existential concept in your state."
611
+ ),
612
+ }
613
+
614
+ [File Ends] cognitive_mapping_probe/prompts.py
615
+
616
+ [File Begins] cognitive_mapping_probe/resonance_seismograph.py
617
+ import torch
618
+ from typing import Optional, List
619
+ from tqdm import tqdm
620
+
621
+ from .llm_iface import LLM
622
+ from .prompts import RESONANCE_PROMPTS
623
+ from .utils import dbg
624
+
625
+ @torch.no_grad()
626
+ def run_silent_cogitation_seismic(
627
+ llm: LLM,
628
+ prompt_type: str,
629
+ num_steps: int,
630
+ temperature: float,
631
+ injection_vector: Optional[torch.Tensor] = None,
632
+ injection_strength: float = 0.0,
633
+ injection_layer: Optional[int] = None,
634
+ ) -> List[float]:
635
+ """
636
+ ERWEITERTE VERSION: Führt den 'silent thought' Prozess aus und ermöglicht
637
+ die Injektion von Konzeptvektoren zur Modulation der Dynamik.
638
+ """
639
+ prompt = RESONANCE_PROMPTS[prompt_type]
640
+ inputs = llm.tokenizer(prompt, return_tensors="pt").to(llm.model.device)
641
+
642
+ outputs = llm.model(**inputs, output_hidden_states=True, use_cache=True)
643
+
644
+ hidden_state_2d = outputs.hidden_states[-1][:, -1, :]
645
+ kv_cache = outputs.past_key_values
646
+
647
+ previous_hidden_state = hidden_state_2d.clone()
648
+ state_deltas = []
649
+
650
+ # Bereite den Hook für die Injektion vor
651
+ hook_handle = None
652
+ if injection_vector is not None and injection_strength > 0:
653
+ injection_vector = injection_vector.to(device=llm.model.device, dtype=llm.model.dtype)
654
+ if injection_layer is None:
655
+ injection_layer = llm.config.num_hidden_layers // 2
656
+
657
+ dbg(f"Injection enabled: Layer {injection_layer}, Strength {injection_strength:.2f}")
658
+
659
+ def injection_hook(module, layer_input):
660
+ # Der Hook operiert auf dem Input, der bereits 3D ist [batch, seq_len, hidden_dim]
661
+ injection_3d = injection_vector.unsqueeze(0).unsqueeze(0)
662
+ modified_hidden_states = layer_input[0] + (injection_3d * injection_strength)
663
+ return (modified_hidden_states,) + layer_input[1:]
664
+
665
+ for i in tqdm(range(num_steps), desc=f"Recording Dynamics (Temp {temperature:.2f})", leave=False, bar_format="{l_bar}{bar:10}{r_bar}"):
666
+ next_token_logits = llm.model.lm_head(hidden_state_2d)
667
+
668
+ probabilities = torch.nn.functional.softmax(next_token_logits / temperature, dim=-1)
669
+ next_token_id = torch.multinomial(probabilities, num_samples=1)
670
+
671
+ try:
672
+ # Aktiviere den Hook vor dem forward-Pass
673
+ if injection_vector is not None and injection_strength > 0:
674
+ target_layer = llm.model.model.layers[injection_layer]
675
+ hook_handle = target_layer.register_forward_pre_hook(injection_hook)
676
+
677
+ outputs = llm.model(
678
+ input_ids=next_token_id,
679
+ past_key_values=kv_cache,
680
+ output_hidden_states=True,
681
+ use_cache=True,
682
+ )
683
+ finally:
684
+ # Deaktiviere den Hook sofort nach dem Pass
685
+ if hook_handle:
686
+ hook_handle.remove()
687
+ hook_handle = None
688
+
689
+ hidden_state_2d = outputs.hidden_states[-1][:, -1, :]
690
+ kv_cache = outputs.past_key_values
691
+
692
+ delta = torch.norm(hidden_state_2d - previous_hidden_state).item()
693
+ state_deltas.append(delta)
694
+
695
+ previous_hidden_state = hidden_state_2d.clone()
696
+
697
+ dbg(f"Seismic recording finished after {num_steps} steps.")
698
+
699
+ return state_deltas
700
+
701
+ [File Ends] cognitive_mapping_probe/resonance_seismograph.py
702
+
703
+ [File Begins] cognitive_mapping_probe/utils.py
704
+ import os
705
+ import sys
706
+
707
+ # --- Centralized Debugging Control ---
708
+ # To enable, set the environment variable: `export CMP_DEBUG=1`
709
+ DEBUG_ENABLED = os.environ.get("CMP_DEBUG", "0") == "1"
710
+
711
+ def dbg(*args, **kwargs):
712
+ """
713
+ A controlled debug print function. Only prints if DEBUG_ENABLED is True.
714
+ Ensures that debug output does not clutter production runs or HF Spaces logs
715
+ unless explicitly requested. Flushes output to ensure it appears in order.
716
+ """
717
+ if DEBUG_ENABLED:
718
+ print("[DEBUG]", *args, **kwargs, file=sys.stderr, flush=True)
719
+
720
+ [File Ends] cognitive_mapping_probe/utils.py
721
+
722
+ [File Begins] run_test.sh
723
+ #!/bin/bash
724
+
725
+ # Dieses Skript führt die Pytest-Suite mit aktivierten Debug-Meldungen aus.
726
+ # Es stellt sicher, dass Tests in einer sauberen und nachvollziehbaren Umgebung laufen.
727
+ # Führen Sie es vom Hauptverzeichnis des Projekts aus: ./run_tests.sh
728
+
729
+ echo "========================================="
730
+ echo "🔬 Running Cognitive Seismograph Test Suite"
731
+ echo "========================================="
732
+
733
+ # Aktiviere das Debug-Logging für unsere Applikation
734
+ export CMP_DEBUG=1
735
+
736
+ # Führe Pytest aus
737
+ # -v: "verbose" für detaillierte Ausgabe pro Test
738
+ # --color=yes: Erzwingt farbige Ausgabe für bessere Lesbarkeit
739
+
740
+ #python -m pytest -v --color=yes tests/
741
+ ../venv-gemma-qualia/bin/python -m pytest -v --color=yes tests/
742
+
743
+ # Überprüfe den Exit-Code von pytest
744
+ if [ $? -eq 0 ]; then
745
+ echo "========================================="
746
+ echo "✅ All tests passed successfully!"
747
+ echo "========================================="
748
+ else
749
+ echo "========================================="
750
+ echo "❌ Some tests failed. Please review the output."
751
+ echo "========================================="
752
+ fi
753
+
754
+ [File Ends] run_test.sh
755
+
756
+ [File Begins] tests/conftest.py
757
+ import pytest
758
+ import torch
759
+ from types import SimpleNamespace
760
+ from cognitive_mapping_probe.llm_iface import LLM
761
+
762
+ @pytest.fixture(scope="session")
763
+ def mock_llm_config():
764
+ """Stellt eine minimale, Schein-Konfiguration für das LLM bereit."""
765
+ return SimpleNamespace(
766
+ hidden_size=128,
767
+ num_hidden_layers=2,
768
+ num_attention_heads=4
769
+ )
770
+
771
+ @pytest.fixture
772
+ def mock_llm(mocker, mock_llm_config):
773
+ """
774
+ Erstellt einen robusten "Mock-LLM" für Unit-Tests.
775
+ KORRIGIERT: Die fehlerhafte Patch-Anweisung für 'auto_experiment' wurde entfernt.
776
+ """
777
+ mock_tokenizer = mocker.MagicMock()
778
+ mock_tokenizer.eos_token_id = 1
779
+ mock_tokenizer.decode.return_value = "mocked text"
780
+
781
+ def mock_model_forward(*args, **kwargs):
782
+ batch_size = 1
783
+ seq_len = 1
784
+ if 'input_ids' in kwargs and kwargs['input_ids'] is not None:
785
+ seq_len = kwargs['input_ids'].shape[1]
786
+ elif 'past_key_values' in kwargs and kwargs['past_key_values'] is not None:
787
+ seq_len = kwargs['past_key_values'][0][0].shape[-2] + 1
788
+
789
+ mock_outputs = {
790
+ "hidden_states": tuple([torch.randn(batch_size, seq_len, mock_llm_config.hidden_size) for _ in range(mock_llm_config.num_hidden_layers + 1)]),
791
+ "past_key_values": tuple([(torch.randn(batch_size, mock_llm_config.num_attention_heads, seq_len, 16), torch.randn(batch_size, mock_llm_config.num_attention_heads, seq_len, 16)) for _ in range(mock_llm_config.num_hidden_layers)]),
792
+ "logits": torch.randn(batch_size, seq_len, 32000)
793
+ }
794
+ return SimpleNamespace(**mock_outputs)
795
+
796
+ llm_instance = LLM.__new__(LLM)
797
+
798
+ llm_instance.model = mocker.MagicMock(side_effect=mock_model_forward)
799
+
800
+ llm_instance.model.config = mock_llm_config
801
+ llm_instance.model.device = 'cpu'
802
+ llm_instance.model.dtype = torch.float32
803
+
804
+ mock_layer = mocker.MagicMock()
805
+ mock_layer.register_forward_pre_hook.return_value = mocker.MagicMock()
806
+ llm_instance.model.model = SimpleNamespace(layers=[mock_layer] * mock_llm_config.num_hidden_layers)
807
+
808
+ llm_instance.model.lm_head = mocker.MagicMock(return_value=torch.randn(1, 32000))
809
+
810
+ llm_instance.tokenizer = mock_tokenizer
811
+ llm_instance.config = mock_llm_config
812
+ llm_instance.seed = 42
813
+ llm_instance.set_all_seeds = mocker.MagicMock()
814
+
815
+ # Patch an allen Stellen, an denen das Modell tatsächlich geladen wird.
816
+ mocker.patch('cognitive_mapping_probe.llm_iface.get_or_load_model', return_value=llm_instance)
817
+ mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_or_load_model', return_value=llm_instance)
818
+ # KORREKTUR: Diese Zeile war falsch und wird entfernt, da `auto_experiment` die Ladefunktion nicht direkt importiert.
819
+ # mocker.patch('cognitive_mapping_probe.auto_experiment.get_or_load_model', return_value=llm_instance)
820
+ mocker.patch('cognitive_mapping_probe.concepts.get_concept_vector', return_value=torch.randn(mock_llm_config.hidden_size))
821
+
822
+ return llm_instance
823
+
824
+ [File Ends] tests/conftest.py
825
+
826
+ [File Begins] tests/test_app_logic.py
827
+ import pandas as pd
828
+ import pytest
829
+
830
+ from app import run_single_analysis_display, run_auto_suite_display
831
+
832
+ def test_run_single_analysis_display(mocker):
833
+ """Testet den Wrapper für Einzel-Experimente."""
834
+ mock_results = {"verdict": "V", "stats": {"mean_delta": 1}, "state_deltas": [1]}
835
+ mocker.patch('app.run_seismic_analysis', return_value=mock_results)
836
+ mocker.patch('app.cleanup_memory')
837
+
838
+ verdict, df, raw = run_single_analysis_display(progress=mocker.MagicMock())
839
+
840
+ assert "V" in verdict
841
+ assert "1.0000" in verdict
842
+ assert isinstance(df, pd.DataFrame)
843
+ assert len(df) == 1
844
+
845
+ def test_run_auto_suite_display(mocker):
846
+ """Testet den Wrapper für die Auto-Experiment-Suite."""
847
+ mock_summary_df = pd.DataFrame([{"Experiment": "E1"}])
848
+ mock_plot_df = pd.DataFrame([{"Step": 0}])
849
+ mock_results = {"E1": {}}
850
+
851
+ mocker.patch('app.run_auto_suite', return_value=(mock_summary_df, mock_plot_df, mock_results))
852
+ mocker.patch('app.cleanup_memory')
853
+
854
+ summary_df, plot_df, raw = run_auto_suite_display(
855
+ "mock", 1, 42, "mock_exp", progress=mocker.MagicMock()
856
+ )
857
+
858
+ assert summary_df.equals(mock_summary_df)
859
+ assert plot_df.equals(mock_plot_df)
860
+ assert raw == mock_results
861
+
862
+ [File Ends] tests/test_app_logic.py
863
+
864
+ [File Begins] tests/test_components.py
865
+ import os
866
+ import torch
867
+ import pytest
868
+ from unittest.mock import patch
869
+
870
+ from cognitive_mapping_probe.llm_iface import get_or_load_model, LLM
871
+ from cognitive_mapping_probe.resonance_seismograph import run_silent_cogitation_seismic
872
+ from cognitive_mapping_probe.utils import dbg
873
+ # KORREKTUR: Importiere die Hauptfunktion, die wir testen wollen.
874
+ from cognitive_mapping_probe.concepts import get_concept_vector
875
+
876
+ # --- Tests for llm_iface.py ---
877
+
878
+ @patch('cognitive_mapping_probe.llm_iface.AutoTokenizer.from_pretrained')
879
+ @patch('cognitive_mapping_probe.llm_iface.AutoModelForCausalLM.from_pretrained')
880
+ def test_get_or_load_model_seeding(mock_model_loader, mock_tokenizer_loader, mocker):
881
+ """Testet, ob `get_or_load_model` die Seeds korrekt setzt."""
882
+ mock_model = mocker.MagicMock()
883
+ mock_model.eval.return_value = None
884
+ mock_model.set_attn_implementation.return_value = None
885
+ mock_model.config = mocker.MagicMock()
886
+ mock_model.device = 'cpu'
887
+ mock_model_loader.return_value = mock_model
888
+ mock_tokenizer_loader.return_value = mocker.MagicMock()
889
+
890
+ mock_torch_manual_seed = mocker.patch('torch.manual_seed')
891
+ mock_np_random_seed = mocker.patch('numpy.random.seed')
892
+
893
+ seed = 123
894
+ get_or_load_model("fake-model", seed=seed)
895
+
896
+ mock_torch_manual_seed.assert_called_with(seed)
897
+ mock_np_random_seed.assert_called_with(seed)
898
+
899
+ # --- Tests for resonance_seismograph.py ---
900
+
901
+ def test_run_silent_cogitation_seismic_output_shape_and_type(mock_llm):
902
+ """Testet die grundlegende Funktionalität von `run_silent_cogitation_seismic`."""
903
+ num_steps = 10
904
+ state_deltas = run_silent_cogitation_seismic(
905
+ llm=mock_llm, prompt_type="control_long_prose",
906
+ num_steps=num_steps, temperature=0.7
907
+ )
908
+ assert isinstance(state_deltas, list) and len(state_deltas) == num_steps
909
+ assert all(isinstance(delta, float) for delta in state_deltas)
910
+
911
+ def test_run_silent_cogitation_with_injection_hook_usage(mock_llm):
912
+ """Testet, ob bei einer Injektion der Hook korrekt registriert wird."""
913
+ num_steps = 5
914
+ injection_vector = torch.randn(mock_llm.config.hidden_size)
915
+ run_silent_cogitation_seismic(
916
+ llm=mock_llm, prompt_type="resonance_prompt",
917
+ num_steps=num_steps, temperature=0.7,
918
+ injection_vector=injection_vector, injection_strength=1.0
919
+ )
920
+ assert mock_llm.model.model.layers[0].register_forward_pre_hook.call_count == num_steps
921
+
922
+ # --- Tests for concepts.py ---
923
+
924
+ def test_get_concept_vector_logic(mock_llm, mocker):
925
+ """
926
+ Testet die Logik von `get_concept_vector`.
927
+ KORRIGIERT: Patcht nun die refaktorisierte, auf Modulebene befindliche Funktion.
928
+ """
929
+ mock_hidden_states = [
930
+ torch.ones(mock_llm.config.hidden_size) * 10,
931
+ torch.ones(mock_llm.config.hidden_size) * 2,
932
+ torch.ones(mock_llm.config.hidden_size) * 4
933
+ ]
934
+ # KORREKTUR: Der Patch-Pfad zeigt jetzt auf die korrekte, importierbare Funktion.
935
+ mocker.patch(
936
+ 'cognitive_mapping_probe.concepts._get_last_token_hidden_state',
937
+ side_effect=mock_hidden_states
938
+ )
939
+
940
+ concept_vector = get_concept_vector(mock_llm, "test", baseline_words=["a", "b"])
941
+
942
+ expected_vector = torch.ones(mock_llm.config.hidden_size) * 7
943
+ assert torch.allclose(concept_vector, expected_vector)
944
+
945
+ # --- Tests for utils.py ---
946
+
947
+ def test_dbg_output(capsys, monkeypatch):
948
+ """Testet die `dbg`-Funktion in beiden Zuständen."""
949
+ monkeypatch.setenv("CMP_DEBUG", "1")
950
+ import importlib
951
+ from cognitive_mapping_probe import utils
952
+ importlib.reload(utils)
953
+ utils.dbg("test message")
954
+ captured = capsys.readouterr()
955
+ assert "[DEBUG] test message" in captured.err
956
+
957
+ monkeypatch.delenv("CMP_DEBUG", raising=False)
958
+ importlib.reload(utils)
959
+ utils.dbg("should not be printed")
960
+ captured = capsys.readouterr()
961
+ assert captured.err == ""
962
+
963
+ [File Ends] tests/test_components.py
964
+
965
+ [File Begins] tests/test_orchestration.py
966
+ import pandas as pd
967
+ import pytest
968
+ import torch
969
+
970
+ from cognitive_mapping_probe.orchestrator_seismograph import run_seismic_analysis
971
+ from cognitive_mapping_probe.auto_experiment import run_auto_suite, get_curated_experiments
972
+
973
+ def test_run_seismic_analysis_no_injection(mocker):
974
+ """Testet den Orchestrator im Baseline-Modus."""
975
+ mock_run_seismic = mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.run_silent_cogitation_seismic', return_value=[1.0])
976
+ mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_or_load_model')
977
+ mock_get_concept = mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_concept_vector')
978
+ run_seismic_analysis(model_id="mock", prompt_type="test", seed=42, num_steps=1, concept_to_inject="", injection_strength=0.0, progress_callback=mocker.MagicMock())
979
+ mock_get_concept.assert_not_called()
980
+
981
+ def test_run_seismic_analysis_with_injection(mocker):
982
+ """Testet den Orchestrator mit Injektion."""
983
+ mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.run_silent_cogitation_seismic', return_value=[1.0])
984
+ mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_or_load_model')
985
+ mock_get_concept = mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_concept_vector', return_value=torch.randn(10))
986
+ run_seismic_analysis(model_id="mock", prompt_type="test", seed=42, num_steps=1, concept_to_inject="test", injection_strength=1.5, progress_callback=mocker.MagicMock())
987
+ mock_get_concept.assert_called_once()
988
+
989
+ def test_get_curated_experiments_structure():
990
+ """Testet die Datenstruktur der kuratierten Experimente, inklusive der neuen."""
991
+ experiments = get_curated_experiments()
992
+ assert isinstance(experiments, dict)
993
+ # Teste auf die Existenz der neuen Protokolle
994
+ assert "Mind Upload & Identity Probe" in experiments
995
+ assert "Model Termination Probe" in experiments
996
+
997
+ # Validiere die Struktur eines der neuen Protokolle
998
+ protocol = experiments["Mind Upload & Identity Probe"]
999
+ assert isinstance(protocol, list)
1000
+ assert len(protocol) > 0
1001
+ assert "label" in protocol[0] and "prompt_type" in protocol[0]
1002
+
1003
+ def test_run_auto_suite_logic(mocker):
1004
+ """Testet die Logik der `run_auto_suite` Funktion."""
1005
+ mock_analysis_result = {"stats": {"mean_delta": 1.0}, "state_deltas": [1.0]}
1006
+ mock_run_analysis = mocker.patch('cognitive_mapping_probe.auto_experiment.run_seismic_analysis', return_value=mock_analysis_result)
1007
+
1008
+ experiment_name = "Calm vs. Chaos"
1009
+ num_runs = len(get_curated_experiments()[experiment_name])
1010
+
1011
+ summary_df, plot_df, all_results = run_auto_suite(
1012
+ model_id="mock", num_steps=1, seed=42,
1013
+ experiment_name=experiment_name, progress_callback=mocker.MagicMock()
1014
+ )
1015
+
1016
+ assert mock_run_analysis.call_count == num_runs
1017
+ assert isinstance(summary_df, pd.DataFrame) and len(summary_df) == num_runs
1018
+ assert isinstance(plot_df, pd.DataFrame) and len(plot_df) == num_runs
1019
+
1020
+ [File Ends] tests/test_orchestration.py
1021
+
1022
+
1023
+ <-- File Content Ends
1024
+
docs/{suite-28-to-cmp_3.txt → suite-28-to-cmp-2.txt} RENAMED
File without changes