Spaces:
Sleeping
Sleeping
Commit
·
21e8595
1
Parent(s):
a345062
cs 1.0
Browse files- app.py +25 -21
- cognitive_mapping_probe/__pycache__/concepts.cpython-310.pyc +0 -0
- cognitive_mapping_probe/orchestrator_seismograph.py +20 -8
- cognitive_mapping_probe/resonance_seismograph.py +37 -9
- tests/conftest.py +8 -23
- tests/test_app_logic.py +5 -20
- tests/test_dynamics.py +0 -60
- tests/test_integration.py +6 -6
- tests/test_orchestration.py +46 -22
app.py
CHANGED
|
@@ -18,31 +18,37 @@ def run_and_display(
|
|
| 18 |
prompt_type: str,
|
| 19 |
seed: int,
|
| 20 |
num_steps: int,
|
|
|
|
|
|
|
| 21 |
progress=gr.Progress(track_tqdm=True)
|
| 22 |
):
|
| 23 |
"""
|
| 24 |
-
Führt die
|
| 25 |
"""
|
| 26 |
try:
|
| 27 |
results = run_seismic_analysis(
|
| 28 |
-
model_id,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
)
|
| 30 |
|
| 31 |
verdict = results.get("verdict", "Analysis complete.")
|
| 32 |
stats = results.get("stats", {})
|
| 33 |
deltas = results.get("state_deltas", [])
|
| 34 |
|
| 35 |
-
# Erstelle einen DataFrame für den Plot
|
| 36 |
df = pd.DataFrame({
|
| 37 |
"Internal Step": range(len(deltas)),
|
| 38 |
"State Change (Delta)": deltas
|
| 39 |
})
|
| 40 |
|
| 41 |
-
# Erstelle eine Zusammenfassung der Statistiken
|
| 42 |
stats_md = f"### Statistical Signature\n"
|
| 43 |
-
stats_md += f"- **Mean Delta:** {stats.get('mean_delta', 0):.4f}
|
| 44 |
-
stats_md += f"- **Std Dev Delta:** {stats.get('std_delta', 0):.4f}
|
| 45 |
-
stats_md += f"- **Max Delta:** {stats.get('max_delta', 0):.4f}
|
| 46 |
|
| 47 |
return f"{verdict}\n\n{stats_md}", df, results
|
| 48 |
|
|
@@ -51,22 +57,23 @@ def run_and_display(
|
|
| 51 |
return f"### ❌ Analysis Failed\nAn unexpected error occurred:\n\n```\n{error_str}\n```", pd.DataFrame(), {}
|
| 52 |
|
| 53 |
# --- Gradio App Definition ---
|
| 54 |
-
with gr.Blocks(theme=theme, title="Cognitive Seismograph") as demo:
|
| 55 |
-
gr.Markdown("# 🧠 Cognitive Seismograph:
|
| 56 |
gr.Markdown(
|
| 57 |
-
"**Neues Paradigma:** Wir
|
| 58 |
)
|
| 59 |
with gr.Row(variant='panel'):
|
| 60 |
with gr.Column(scale=1):
|
| 61 |
-
gr.Markdown("### Parameters")
|
| 62 |
model_id_input = gr.Textbox(value="google/gemma-3-1b-it", label="Model ID")
|
| 63 |
-
prompt_type_input = gr.Radio(
|
| 64 |
-
choices=list(RESONANCE_PROMPTS.keys()),
|
| 65 |
-
value="control_long_prose",
|
| 66 |
-
label="Prompt Type (Cognitive Load)"
|
| 67 |
-
)
|
| 68 |
seed_input = gr.Slider(1, 1000, 42, step=1, label="Seed")
|
| 69 |
num_steps_input = gr.Slider(50, 1000, 300, step=10, label="Number of Internal Steps")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
run_btn = gr.Button("Run Seismic Analysis", variant="primary")
|
| 71 |
|
| 72 |
with gr.Column(scale=2):
|
|
@@ -84,15 +91,12 @@ with gr.Blocks(theme=theme, title="Cognitive Seismograph") as demo:
|
|
| 84 |
|
| 85 |
run_btn.click(
|
| 86 |
fn=run_and_display,
|
| 87 |
-
inputs=[model_id_input, prompt_type_input, seed_input, num_steps_input],
|
| 88 |
outputs=[verdict_output, plot_output, raw_json_output]
|
| 89 |
)
|
| 90 |
|
| 91 |
if __name__ == "__main__":
|
| 92 |
-
# Die Pre-Flight Checks sind nun entfernt, da das neue Paradigma keine Konvergenz mehr erfordert.
|
| 93 |
print("="*80)
|
| 94 |
-
print("🔬 COGNITIVE SEISMOGRAPH INITIALIZED")
|
| 95 |
print("="*80)
|
| 96 |
-
print("Das experimentelle Paradigma wurde aufgrund der Falsifikation der Konvergenz-Hypothese geändert.")
|
| 97 |
-
print("Wir messen nun die Dynamik des nicht-konvergenten Zustands.")
|
| 98 |
demo.launch(server_name="0.0.0.0", server_port=7860, debug=True)
|
|
|
|
| 18 |
prompt_type: str,
|
| 19 |
seed: int,
|
| 20 |
num_steps: int,
|
| 21 |
+
concept_to_inject: str,
|
| 22 |
+
injection_strength: float,
|
| 23 |
progress=gr.Progress(track_tqdm=True)
|
| 24 |
):
|
| 25 |
"""
|
| 26 |
+
Führt die seismische Analyse durch, inklusive der optionalen Konzeptinjektion.
|
| 27 |
"""
|
| 28 |
try:
|
| 29 |
results = run_seismic_analysis(
|
| 30 |
+
model_id=model_id,
|
| 31 |
+
prompt_type=prompt_type,
|
| 32 |
+
seed=int(seed),
|
| 33 |
+
num_steps=int(num_steps),
|
| 34 |
+
concept_to_inject=concept_to_inject,
|
| 35 |
+
injection_strength=float(injection_strength),
|
| 36 |
+
progress_callback=progress
|
| 37 |
)
|
| 38 |
|
| 39 |
verdict = results.get("verdict", "Analysis complete.")
|
| 40 |
stats = results.get("stats", {})
|
| 41 |
deltas = results.get("state_deltas", [])
|
| 42 |
|
|
|
|
| 43 |
df = pd.DataFrame({
|
| 44 |
"Internal Step": range(len(deltas)),
|
| 45 |
"State Change (Delta)": deltas
|
| 46 |
})
|
| 47 |
|
|
|
|
| 48 |
stats_md = f"### Statistical Signature\n"
|
| 49 |
+
stats_md += f"- **Mean Delta:** {stats.get('mean_delta', 0):.4f}\n"
|
| 50 |
+
stats_md += f"- **Std Dev Delta:** {stats.get('std_delta', 0):.4f}\n"
|
| 51 |
+
stats_md += f"- **Max Delta:** {stats.get('max_delta', 0):.4f}\n"
|
| 52 |
|
| 53 |
return f"{verdict}\n\n{stats_md}", df, results
|
| 54 |
|
|
|
|
| 57 |
return f"### ❌ Analysis Failed\nAn unexpected error occurred:\n\n```\n{error_str}\n```", pd.DataFrame(), {}
|
| 58 |
|
| 59 |
# --- Gradio App Definition ---
|
| 60 |
+
with gr.Blocks(theme=theme, title="Cognitive Seismograph 2.0") as demo:
|
| 61 |
+
gr.Markdown("# 🧠 Cognitive Seismograph 2.0: Modulating Internal Dynamics")
|
| 62 |
gr.Markdown(
|
| 63 |
+
"**Neues Paradigma:** Wir messen nicht nur die intrinsische Dynamik, sondern versuchen sie aktiv durch **Konzeptinjektionen** zu modulieren. Vergleiche die 'seismische Signatur' mit und ohne Injektion."
|
| 64 |
)
|
| 65 |
with gr.Row(variant='panel'):
|
| 66 |
with gr.Column(scale=1):
|
| 67 |
+
gr.Markdown("### 1. General Parameters")
|
| 68 |
model_id_input = gr.Textbox(value="google/gemma-3-1b-it", label="Model ID")
|
| 69 |
+
prompt_type_input = gr.Radio(choices=list(RESONANCE_PROMPTS.keys()), value="resonance_prompt", label="Prompt Type")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
seed_input = gr.Slider(1, 1000, 42, step=1, label="Seed")
|
| 71 |
num_steps_input = gr.Slider(50, 1000, 300, step=10, label="Number of Internal Steps")
|
| 72 |
+
|
| 73 |
+
gr.Markdown("### 2. Modulation Parameters")
|
| 74 |
+
concept_input = gr.Textbox(label="Concept to Inject", placeholder="e.g., 'solitude' or 'ocean' (leave blank for baseline)")
|
| 75 |
+
strength_input = gr.Slider(0.0, 5.0, 1.0, step=0.1, label="Injection Strength")
|
| 76 |
+
|
| 77 |
run_btn = gr.Button("Run Seismic Analysis", variant="primary")
|
| 78 |
|
| 79 |
with gr.Column(scale=2):
|
|
|
|
| 91 |
|
| 92 |
run_btn.click(
|
| 93 |
fn=run_and_display,
|
| 94 |
+
inputs=[model_id_input, prompt_type_input, seed_input, num_steps_input, concept_input, strength_input],
|
| 95 |
outputs=[verdict_output, plot_output, raw_json_output]
|
| 96 |
)
|
| 97 |
|
| 98 |
if __name__ == "__main__":
|
|
|
|
| 99 |
print("="*80)
|
| 100 |
+
print("🔬 COGNITIVE SEISMOGRAPH 2.0 (MODULATION-ENABLED) INITIALIZED")
|
| 101 |
print("="*80)
|
|
|
|
|
|
|
| 102 |
demo.launch(server_name="0.0.0.0", server_port=7860, debug=True)
|
cognitive_mapping_probe/__pycache__/concepts.cpython-310.pyc
CHANGED
|
Binary files a/cognitive_mapping_probe/__pycache__/concepts.cpython-310.pyc and b/cognitive_mapping_probe/__pycache__/concepts.cpython-310.pyc differ
|
|
|
cognitive_mapping_probe/orchestrator_seismograph.py
CHANGED
|
@@ -4,6 +4,8 @@ from typing import Dict, Any
|
|
| 4 |
|
| 5 |
from .llm_iface import get_or_load_model
|
| 6 |
from .resonance_seismograph import run_silent_cogitation_seismic
|
|
|
|
|
|
|
| 7 |
from .utils import dbg
|
| 8 |
|
| 9 |
def run_seismic_analysis(
|
|
@@ -11,28 +13,37 @@ def run_seismic_analysis(
|
|
| 11 |
prompt_type: str,
|
| 12 |
seed: int,
|
| 13 |
num_steps: int,
|
|
|
|
|
|
|
| 14 |
progress_callback
|
| 15 |
) -> Dict[str, Any]:
|
| 16 |
"""
|
| 17 |
-
Orchestriert das
|
| 18 |
-
|
| 19 |
"""
|
| 20 |
progress_callback(0.1, desc="Loading model...")
|
| 21 |
llm = get_or_load_model(model_id, seed)
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
progress_callback(0.3, desc=f"Running seismic cogitation for '{prompt_type}'...")
|
| 24 |
|
| 25 |
-
# Der Resonanz-Loop gibt nun die volle Zeitreihe der Deltas zurück
|
| 26 |
state_deltas = run_silent_cogitation_seismic(
|
| 27 |
-
llm,
|
| 28 |
prompt_type=prompt_type,
|
| 29 |
num_steps=num_steps,
|
| 30 |
-
temperature=0.1,
|
|
|
|
|
|
|
|
|
|
| 31 |
)
|
| 32 |
|
| 33 |
progress_callback(0.9, desc="Analyzing dynamics...")
|
| 34 |
|
| 35 |
-
# Statistische Analyse der Zeitreihe
|
| 36 |
if state_deltas:
|
| 37 |
deltas_np = np.array(state_deltas)
|
| 38 |
stats = {
|
|
@@ -42,6 +53,8 @@ def run_seismic_analysis(
|
|
| 42 |
"min_delta": float(np.min(deltas_np)),
|
| 43 |
}
|
| 44 |
verdict = f"### ✅ Seismic Analysis Complete\nDie interne Dynamik für '{prompt_type}' wurde über {len(deltas_np)} Schritte aufgezeichnet."
|
|
|
|
|
|
|
| 45 |
else:
|
| 46 |
stats = {}
|
| 47 |
verdict = "### ⚠️ Analysis Warning\nKeine Zustandsänderungen aufgezeichnet."
|
|
@@ -52,8 +65,7 @@ def run_seismic_analysis(
|
|
| 52 |
"state_deltas": state_deltas
|
| 53 |
}
|
| 54 |
|
| 55 |
-
dbg("--- Seismic Analysis Results ---")
|
| 56 |
-
dbg(results)
|
| 57 |
|
| 58 |
del llm
|
| 59 |
if torch.cuda.is_available():
|
|
|
|
| 4 |
|
| 5 |
from .llm_iface import get_or_load_model
|
| 6 |
from .resonance_seismograph import run_silent_cogitation_seismic
|
| 7 |
+
# WIEDERHERGESTELLTER IMPORT
|
| 8 |
+
from .concepts import get_concept_vector
|
| 9 |
from .utils import dbg
|
| 10 |
|
| 11 |
def run_seismic_analysis(
|
|
|
|
| 13 |
prompt_type: str,
|
| 14 |
seed: int,
|
| 15 |
num_steps: int,
|
| 16 |
+
concept_to_inject: str,
|
| 17 |
+
injection_strength: float,
|
| 18 |
progress_callback
|
| 19 |
) -> Dict[str, Any]:
|
| 20 |
"""
|
| 21 |
+
Orchestriert das "Cognitive Seismograph"-Experiment, jetzt mit optionaler
|
| 22 |
+
Konzeptinjektion zur Modulation der Dynamik.
|
| 23 |
"""
|
| 24 |
progress_callback(0.1, desc="Loading model...")
|
| 25 |
llm = get_or_load_model(model_id, seed)
|
| 26 |
|
| 27 |
+
# Lade den Konzeptvektor, falls ein Konzept angegeben wurde
|
| 28 |
+
injection_vector = None
|
| 29 |
+
if concept_to_inject and concept_to_inject.strip():
|
| 30 |
+
progress_callback(0.2, desc=f"Extracting vector for '{concept_to_inject}'...")
|
| 31 |
+
injection_vector = get_concept_vector(llm, concept_to_inject.strip())
|
| 32 |
+
|
| 33 |
progress_callback(0.3, desc=f"Running seismic cogitation for '{prompt_type}'...")
|
| 34 |
|
|
|
|
| 35 |
state_deltas = run_silent_cogitation_seismic(
|
| 36 |
+
llm=llm,
|
| 37 |
prompt_type=prompt_type,
|
| 38 |
num_steps=num_steps,
|
| 39 |
+
temperature=0.1,
|
| 40 |
+
# Übergebe die neuen Parameter an den Resonanz-Loop
|
| 41 |
+
injection_vector=injection_vector,
|
| 42 |
+
injection_strength=injection_strength
|
| 43 |
)
|
| 44 |
|
| 45 |
progress_callback(0.9, desc="Analyzing dynamics...")
|
| 46 |
|
|
|
|
| 47 |
if state_deltas:
|
| 48 |
deltas_np = np.array(state_deltas)
|
| 49 |
stats = {
|
|
|
|
| 53 |
"min_delta": float(np.min(deltas_np)),
|
| 54 |
}
|
| 55 |
verdict = f"### ✅ Seismic Analysis Complete\nDie interne Dynamik für '{prompt_type}' wurde über {len(deltas_np)} Schritte aufgezeichnet."
|
| 56 |
+
if injection_vector is not None:
|
| 57 |
+
verdict += f"\nModuliert mit dem Konzept **'{concept_to_inject}'** bei Stärke **{injection_strength:.2f}**."
|
| 58 |
else:
|
| 59 |
stats = {}
|
| 60 |
verdict = "### ⚠️ Analysis Warning\nKeine Zustandsänderungen aufgezeichnet."
|
|
|
|
| 65 |
"state_deltas": state_deltas
|
| 66 |
}
|
| 67 |
|
| 68 |
+
dbg("--- Seismic Analysis Results ---", results)
|
|
|
|
| 69 |
|
| 70 |
del llm
|
| 71 |
if torch.cuda.is_available():
|
cognitive_mapping_probe/resonance_seismograph.py
CHANGED
|
@@ -12,10 +12,13 @@ def run_silent_cogitation_seismic(
|
|
| 12 |
prompt_type: str,
|
| 13 |
num_steps: int,
|
| 14 |
temperature: float,
|
|
|
|
|
|
|
|
|
|
| 15 |
) -> List[float]:
|
| 16 |
"""
|
| 17 |
-
|
| 18 |
-
|
| 19 |
"""
|
| 20 |
prompt = RESONANCE_PROMPTS[prompt_type]
|
| 21 |
inputs = llm.tokenizer(prompt, return_tensors="pt").to(llm.model.device)
|
|
@@ -28,19 +31,44 @@ def run_silent_cogitation_seismic(
|
|
| 28 |
previous_hidden_state = hidden_state_2d.clone()
|
| 29 |
state_deltas = []
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
for i in tqdm(range(num_steps), desc=f"Recording Dynamics (Temp {temperature:.2f})", leave=False, bar_format="{l_bar}{bar:10}{r_bar}"):
|
| 32 |
next_token_logits = llm.model.lm_head(hidden_state_2d)
|
| 33 |
|
| 34 |
-
# Wir verwenden immer stochastisches Sampling, um die Dynamik zu erfassen
|
| 35 |
probabilities = torch.nn.functional.softmax(next_token_logits / temperature, dim=-1)
|
| 36 |
next_token_id = torch.multinomial(probabilities, num_samples=1)
|
| 37 |
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
hidden_state_2d = outputs.hidden_states[-1][:, -1, :]
|
| 46 |
kv_cache = outputs.past_key_values
|
|
|
|
| 12 |
prompt_type: str,
|
| 13 |
num_steps: int,
|
| 14 |
temperature: float,
|
| 15 |
+
injection_vector: Optional[torch.Tensor] = None,
|
| 16 |
+
injection_strength: float = 0.0,
|
| 17 |
+
injection_layer: Optional[int] = None,
|
| 18 |
) -> List[float]:
|
| 19 |
"""
|
| 20 |
+
ERWEITERTE VERSION: Führt den 'silent thought' Prozess aus und ermöglicht
|
| 21 |
+
die Injektion von Konzeptvektoren zur Modulation der Dynamik.
|
| 22 |
"""
|
| 23 |
prompt = RESONANCE_PROMPTS[prompt_type]
|
| 24 |
inputs = llm.tokenizer(prompt, return_tensors="pt").to(llm.model.device)
|
|
|
|
| 31 |
previous_hidden_state = hidden_state_2d.clone()
|
| 32 |
state_deltas = []
|
| 33 |
|
| 34 |
+
# Bereite den Hook für die Injektion vor
|
| 35 |
+
hook_handle = None
|
| 36 |
+
if injection_vector is not None and injection_strength > 0:
|
| 37 |
+
injection_vector = injection_vector.to(device=llm.model.device, dtype=llm.model.dtype)
|
| 38 |
+
if injection_layer is None:
|
| 39 |
+
injection_layer = llm.config.num_hidden_layers // 2
|
| 40 |
+
|
| 41 |
+
dbg(f"Injection enabled: Layer {injection_layer}, Strength {injection_strength:.2f}")
|
| 42 |
+
|
| 43 |
+
def injection_hook(module, layer_input):
|
| 44 |
+
# Der Hook operiert auf dem Input, der bereits 3D ist [batch, seq_len, hidden_dim]
|
| 45 |
+
injection_3d = injection_vector.unsqueeze(0).unsqueeze(0)
|
| 46 |
+
modified_hidden_states = layer_input[0] + (injection_3d * injection_strength)
|
| 47 |
+
return (modified_hidden_states,) + layer_input[1:]
|
| 48 |
+
|
| 49 |
for i in tqdm(range(num_steps), desc=f"Recording Dynamics (Temp {temperature:.2f})", leave=False, bar_format="{l_bar}{bar:10}{r_bar}"):
|
| 50 |
next_token_logits = llm.model.lm_head(hidden_state_2d)
|
| 51 |
|
|
|
|
| 52 |
probabilities = torch.nn.functional.softmax(next_token_logits / temperature, dim=-1)
|
| 53 |
next_token_id = torch.multinomial(probabilities, num_samples=1)
|
| 54 |
|
| 55 |
+
try:
|
| 56 |
+
# Aktiviere den Hook vor dem forward-Pass
|
| 57 |
+
if injection_vector is not None and injection_strength > 0:
|
| 58 |
+
target_layer = llm.model.model.layers[injection_layer]
|
| 59 |
+
hook_handle = target_layer.register_forward_pre_hook(injection_hook)
|
| 60 |
+
|
| 61 |
+
outputs = llm.model(
|
| 62 |
+
input_ids=next_token_id,
|
| 63 |
+
past_key_values=kv_cache,
|
| 64 |
+
output_hidden_states=True,
|
| 65 |
+
use_cache=True,
|
| 66 |
+
)
|
| 67 |
+
finally:
|
| 68 |
+
# Deaktiviere den Hook sofort nach dem Pass
|
| 69 |
+
if hook_handle:
|
| 70 |
+
hook_handle.remove()
|
| 71 |
+
hook_handle = None
|
| 72 |
|
| 73 |
hidden_state_2d = outputs.hidden_states[-1][:, -1, :]
|
| 74 |
kv_cache = outputs.past_key_values
|
tests/conftest.py
CHANGED
|
@@ -16,38 +16,25 @@ def mock_llm_config():
|
|
| 16 |
def mock_llm(mocker, mock_llm_config):
|
| 17 |
"""
|
| 18 |
Erstellt einen schnellen "Mock-LLM" für Unit-Tests.
|
| 19 |
-
ERWEITERT: Patcht nun
|
| 20 |
-
um in allen Testdateien zu funktionieren.
|
| 21 |
"""
|
| 22 |
mock_tokenizer = mocker.MagicMock()
|
| 23 |
mock_tokenizer.eos_token_id = 1
|
| 24 |
|
| 25 |
def mock_model_forward(*args, **kwargs):
|
| 26 |
batch_size = 1
|
| 27 |
-
if 'input_ids' in kwargs:
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
seq_len = kwargs['past_key_values'][0][0].shape[-2] + 1
|
| 31 |
-
else:
|
| 32 |
-
seq_len = 1
|
| 33 |
|
| 34 |
mock_outputs = {
|
| 35 |
-
"hidden_states": tuple(
|
| 36 |
-
|
| 37 |
-
),
|
| 38 |
-
"past_key_values": tuple(
|
| 39 |
-
[
|
| 40 |
-
(torch.randn(batch_size, mock_llm_config.num_attention_heads, seq_len, 16),
|
| 41 |
-
torch.randn(batch_size, mock_llm_config.num_attention_heads, seq_len, 16))
|
| 42 |
-
for _ in range(mock_llm_config.num_hidden_layers)
|
| 43 |
-
]
|
| 44 |
-
),
|
| 45 |
"logits": torch.randn(batch_size, seq_len, 32000)
|
| 46 |
}
|
| 47 |
return SimpleNamespace(**mock_outputs)
|
| 48 |
|
| 49 |
llm_instance = LLM.__new__(LLM)
|
| 50 |
-
|
| 51 |
llm_instance.model = mock_model_forward
|
| 52 |
llm_instance.model.config = mock_llm_config
|
| 53 |
llm_instance.model.device = 'cpu'
|
|
@@ -61,10 +48,8 @@ def mock_llm(mocker, mock_llm_config):
|
|
| 61 |
llm_instance.seed = 42
|
| 62 |
llm_instance.set_all_seeds = mocker.MagicMock()
|
| 63 |
|
| 64 |
-
# ERWEITERUNG: Stelle sicher, dass `get_or_load_model` an allen Orten gepatcht wird.
|
| 65 |
-
mocker.patch('cognitive_mapping_probe.llm_iface.get_or_load_model', return_value=llm_instance)
|
| 66 |
mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_or_load_model', return_value=llm_instance)
|
| 67 |
-
#
|
| 68 |
-
mocker.patch('cognitive_mapping_probe.
|
| 69 |
|
| 70 |
return llm_instance
|
|
|
|
| 16 |
def mock_llm(mocker, mock_llm_config):
|
| 17 |
"""
|
| 18 |
Erstellt einen schnellen "Mock-LLM" für Unit-Tests.
|
| 19 |
+
ERWEITERT: Patcht nun auch die `concepts`-Abhängigkeit.
|
|
|
|
| 20 |
"""
|
| 21 |
mock_tokenizer = mocker.MagicMock()
|
| 22 |
mock_tokenizer.eos_token_id = 1
|
| 23 |
|
| 24 |
def mock_model_forward(*args, **kwargs):
|
| 25 |
batch_size = 1
|
| 26 |
+
if 'input_ids' in kwargs: seq_len = kwargs['input_ids'].shape[1]
|
| 27 |
+
elif 'past_key_values' in kwargs: seq_len = kwargs['past_key_values'][0][0].shape[-2] + 1
|
| 28 |
+
else: seq_len = 1
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
mock_outputs = {
|
| 31 |
+
"hidden_states": tuple([torch.randn(batch_size, seq_len, mock_llm_config.hidden_size) for _ in range(mock_llm_config.num_hidden_layers + 1)]),
|
| 32 |
+
"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)]),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
"logits": torch.randn(batch_size, seq_len, 32000)
|
| 34 |
}
|
| 35 |
return SimpleNamespace(**mock_outputs)
|
| 36 |
|
| 37 |
llm_instance = LLM.__new__(LLM)
|
|
|
|
| 38 |
llm_instance.model = mock_model_forward
|
| 39 |
llm_instance.model.config = mock_llm_config
|
| 40 |
llm_instance.model.device = 'cpu'
|
|
|
|
| 48 |
llm_instance.seed = 42
|
| 49 |
llm_instance.set_all_seeds = mocker.MagicMock()
|
| 50 |
|
|
|
|
|
|
|
| 51 |
mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_or_load_model', return_value=llm_instance)
|
| 52 |
+
# Patch für die wiederhergestellte `concepts`-Funktion
|
| 53 |
+
mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_concept_vector', return_value=torch.randn(mock_llm_config.hidden_size))
|
| 54 |
|
| 55 |
return llm_instance
|
tests/test_app_logic.py
CHANGED
|
@@ -13,42 +13,27 @@ def test_run_and_display_logic(mocker):
|
|
| 13 |
# 1. Definiere die Schein-Ausgabe, die `run_seismic_analysis` zurückgeben soll
|
| 14 |
mock_results = {
|
| 15 |
"verdict": "Mock Verdict",
|
| 16 |
-
"stats": {
|
| 17 |
-
"mean_delta": 0.5,
|
| 18 |
-
"std_delta": 0.1,
|
| 19 |
-
"max_delta": 1.0,
|
| 20 |
-
},
|
| 21 |
"state_deltas": [0.4, 0.5, 0.6]
|
| 22 |
}
|
| 23 |
mocker.patch('app.run_seismic_analysis', return_value=mock_results)
|
| 24 |
|
| 25 |
-
# Mocke den Gradio Progress-Callback
|
| 26 |
mock_progress = mocker.MagicMock()
|
| 27 |
|
| 28 |
-
# 2. Rufe die zu testende Funktion auf
|
| 29 |
verdict_md, plot_df, raw_json = run_and_display(
|
| 30 |
model_id="mock_model",
|
| 31 |
prompt_type="mock_prompt",
|
| 32 |
seed=42,
|
| 33 |
num_steps=3,
|
|
|
|
|
|
|
| 34 |
progress=mock_progress
|
| 35 |
)
|
| 36 |
|
| 37 |
-
# 3. Validiere die Ausgaben
|
| 38 |
-
|
| 39 |
-
# ASSERT 1: Die Markdown-Ausgabe muss die korrekten Statistiken enthalten
|
| 40 |
assert "Mock Verdict" in verdict_md
|
| 41 |
-
assert "Mean Delta:" in verdict_md
|
| 42 |
assert "0.5000" in verdict_md
|
| 43 |
-
assert "Std Dev Delta:" in verdict_md
|
| 44 |
-
assert "0.1000" in verdict_md
|
| 45 |
-
|
| 46 |
-
# ASSERT 2: Der Pandas DataFrame für den Plot muss korrekt erstellt werden
|
| 47 |
assert isinstance(plot_df, pd.DataFrame)
|
| 48 |
-
assert "Internal Step" in plot_df.columns
|
| 49 |
-
assert "State Change (Delta)" in plot_df.columns
|
| 50 |
assert len(plot_df) == 3
|
| 51 |
-
assert plot_df["State Change (Delta)"].tolist() == [0.4, 0.5, 0.6]
|
| 52 |
-
|
| 53 |
-
# ASSERT 3: Die Raw-JSON-Ausgabe muss die Originaldaten enthalten
|
| 54 |
assert raw_json == mock_results
|
|
|
|
| 13 |
# 1. Definiere die Schein-Ausgabe, die `run_seismic_analysis` zurückgeben soll
|
| 14 |
mock_results = {
|
| 15 |
"verdict": "Mock Verdict",
|
| 16 |
+
"stats": { "mean_delta": 0.5, "std_delta": 0.1, "max_delta": 1.0, },
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
"state_deltas": [0.4, 0.5, 0.6]
|
| 18 |
}
|
| 19 |
mocker.patch('app.run_seismic_analysis', return_value=mock_results)
|
| 20 |
|
|
|
|
| 21 |
mock_progress = mocker.MagicMock()
|
| 22 |
|
| 23 |
+
# 2. Rufe die zu testende Funktion mit den KORRIGIERTEN Argumenten auf
|
| 24 |
verdict_md, plot_df, raw_json = run_and_display(
|
| 25 |
model_id="mock_model",
|
| 26 |
prompt_type="mock_prompt",
|
| 27 |
seed=42,
|
| 28 |
num_steps=3,
|
| 29 |
+
concept_to_inject="", # Fehlendes Argument hinzugefügt
|
| 30 |
+
injection_strength=0.0, # Fehlendes Argument hinzugefügt
|
| 31 |
progress=mock_progress
|
| 32 |
)
|
| 33 |
|
| 34 |
+
# 3. Validiere die Ausgaben
|
|
|
|
|
|
|
| 35 |
assert "Mock Verdict" in verdict_md
|
|
|
|
| 36 |
assert "0.5000" in verdict_md
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
assert isinstance(plot_df, pd.DataFrame)
|
|
|
|
|
|
|
| 38 |
assert len(plot_df) == 3
|
|
|
|
|
|
|
|
|
|
| 39 |
assert raw_json == mock_results
|
tests/test_dynamics.py
DELETED
|
@@ -1,60 +0,0 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
import numpy as np
|
| 3 |
-
import pytest
|
| 4 |
-
from types import SimpleNamespace
|
| 5 |
-
|
| 6 |
-
from cognitive_mapping_probe.resonance_seismograph import run_silent_cogitation_seismic
|
| 7 |
-
from cognitive_mapping_probe.orchestrator_seismograph import run_seismic_analysis
|
| 8 |
-
|
| 9 |
-
def test_run_silent_cogitation_seismic_output(mock_llm):
|
| 10 |
-
"""
|
| 11 |
-
Testet die Kernfunktion `run_silent_cogitation_seismic`.
|
| 12 |
-
ASSERT: Gibt eine Liste von Floats zurück, deren Länge der Anzahl der Schritte entspricht.
|
| 13 |
-
"""
|
| 14 |
-
num_steps = 10
|
| 15 |
-
state_deltas = run_silent_cogitation_seismic(
|
| 16 |
-
llm=mock_llm,
|
| 17 |
-
prompt_type="control_long_prose",
|
| 18 |
-
num_steps=num_steps,
|
| 19 |
-
temperature=0.7
|
| 20 |
-
)
|
| 21 |
-
|
| 22 |
-
assert isinstance(state_deltas, list)
|
| 23 |
-
assert len(state_deltas) == num_steps
|
| 24 |
-
assert all(isinstance(delta, float) for delta in state_deltas)
|
| 25 |
-
|
| 26 |
-
def test_seismic_analysis_orchestrator(mocker, mock_llm):
|
| 27 |
-
"""
|
| 28 |
-
Testet den `run_seismic_analysis` Orchestrator.
|
| 29 |
-
Wir mocken die darunterliegende `run_silent_cogitation_seismic`, um das Verhalten
|
| 30 |
-
des Orchestrators isoliert zu prüfen.
|
| 31 |
-
ASSERT: Berechnet korrekte Statistiken und gibt die erwartete Datenstruktur zurück.
|
| 32 |
-
"""
|
| 33 |
-
mock_deltas = [1.0, 2.0, 3.0, 4.0, 5.0]
|
| 34 |
-
mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.run_silent_cogitation_seismic', return_value=mock_deltas)
|
| 35 |
-
|
| 36 |
-
# Mocke den Gradio Progress-Callback
|
| 37 |
-
mock_progress = mocker.MagicMock()
|
| 38 |
-
|
| 39 |
-
results = run_seismic_analysis(
|
| 40 |
-
model_id="mock_model",
|
| 41 |
-
prompt_type="test_prompt",
|
| 42 |
-
seed=42,
|
| 43 |
-
num_steps=5,
|
| 44 |
-
progress_callback=mock_progress
|
| 45 |
-
)
|
| 46 |
-
|
| 47 |
-
# ASSERT: Die Ergebnisse haben die korrekte Struktur und den korrekten Inhalt
|
| 48 |
-
assert "verdict" in results
|
| 49 |
-
assert "stats" in results
|
| 50 |
-
assert "state_deltas" in results
|
| 51 |
-
|
| 52 |
-
stats = results["stats"]
|
| 53 |
-
assert stats["mean_delta"] == pytest.approx(np.mean(mock_deltas))
|
| 54 |
-
assert stats["std_delta"] == pytest.approx(np.std(mock_deltas))
|
| 55 |
-
assert stats["max_delta"] == pytest.approx(max(mock_deltas))
|
| 56 |
-
|
| 57 |
-
assert results["state_deltas"] == mock_deltas
|
| 58 |
-
|
| 59 |
-
# ASSERT: Der Progress-Callback wurde aufgerufen
|
| 60 |
-
assert mock_progress.call_count > 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tests/test_integration.py
CHANGED
|
@@ -9,17 +9,15 @@ def test_end_to_end_with_mock_llm(mock_llm, mocker):
|
|
| 9 |
"""
|
| 10 |
Ein End-to-End-Integrationstest, der den gesamten Datenfluss von der App
|
| 11 |
über den Orchestrator bis zum (gemockten) LLM validiert.
|
| 12 |
-
|
| 13 |
-
Dieser Test ersetzt die Notwendigkeit für `pre_flight_checks.py`, indem er
|
| 14 |
-
die gesamte Kette in einer kontrollierten Testumgebung ausführt.
|
| 15 |
"""
|
| 16 |
-
# 1. Führe den Orchestrator mit dem `mock_llm` aus.
|
| 17 |
-
# Dies ist ein echter Aufruf, keine gemockte Funktion.
|
| 18 |
results = run_seismic_analysis(
|
| 19 |
model_id="mock_model",
|
| 20 |
prompt_type="control_long_prose",
|
| 21 |
seed=42,
|
| 22 |
num_steps=5,
|
|
|
|
|
|
|
| 23 |
progress_callback=mocker.MagicMock()
|
| 24 |
)
|
| 25 |
|
|
@@ -31,12 +29,14 @@ def test_end_to_end_with_mock_llm(mock_llm, mocker):
|
|
| 31 |
# 2. Mocke nun den Orchestrator, um die App-Logik mit seinen Ergebnissen zu füttern
|
| 32 |
mocker.patch('app.run_seismic_analysis', return_value=results)
|
| 33 |
|
| 34 |
-
# 3. Führe die App-Logik aus
|
| 35 |
_, plot_df, _ = run_and_display(
|
| 36 |
model_id="mock_model",
|
| 37 |
prompt_type="control_long_prose",
|
| 38 |
seed=42,
|
| 39 |
num_steps=5,
|
|
|
|
|
|
|
| 40 |
progress=mocker.MagicMock()
|
| 41 |
)
|
| 42 |
|
|
|
|
| 9 |
"""
|
| 10 |
Ein End-to-End-Integrationstest, der den gesamten Datenfluss von der App
|
| 11 |
über den Orchestrator bis zum (gemockten) LLM validiert.
|
|
|
|
|
|
|
|
|
|
| 12 |
"""
|
| 13 |
+
# 1. Führe den Orchestrator mit dem `mock_llm` und den KORRIGIERTEN Argumenten aus.
|
|
|
|
| 14 |
results = run_seismic_analysis(
|
| 15 |
model_id="mock_model",
|
| 16 |
prompt_type="control_long_prose",
|
| 17 |
seed=42,
|
| 18 |
num_steps=5,
|
| 19 |
+
concept_to_inject="test_concept", # Argument hinzugefügt
|
| 20 |
+
injection_strength=1.0, # Argument hinzugefügt
|
| 21 |
progress_callback=mocker.MagicMock()
|
| 22 |
)
|
| 23 |
|
|
|
|
| 29 |
# 2. Mocke nun den Orchestrator, um die App-Logik mit seinen Ergebnissen zu füttern
|
| 30 |
mocker.patch('app.run_seismic_analysis', return_value=results)
|
| 31 |
|
| 32 |
+
# 3. Führe die App-Logik mit den KORRIGIERTEN Argumenten aus
|
| 33 |
_, plot_df, _ = run_and_display(
|
| 34 |
model_id="mock_model",
|
| 35 |
prompt_type="control_long_prose",
|
| 36 |
seed=42,
|
| 37 |
num_steps=5,
|
| 38 |
+
concept_to_inject="test_concept", # Argument hinzugefügt
|
| 39 |
+
injection_strength=1.0, # Argument hinzugefügt
|
| 40 |
progress=mocker.MagicMock()
|
| 41 |
)
|
| 42 |
|
tests/test_orchestration.py
CHANGED
|
@@ -1,43 +1,67 @@
|
|
| 1 |
import numpy as np
|
| 2 |
import pytest
|
|
|
|
| 3 |
from types import SimpleNamespace
|
| 4 |
|
| 5 |
from cognitive_mapping_probe.orchestrator_seismograph import run_seismic_analysis
|
| 6 |
|
| 7 |
-
def
|
| 8 |
"""
|
| 9 |
-
Testet den
|
| 10 |
-
Wir mocken die darunterliegende `run_silent_cogitation_seismic`, um das Verhalten
|
| 11 |
-
des Orchestrators isoliert zu prüfen.
|
| 12 |
-
ASSERT: Berechnet korrekte Statistiken und gibt die erwartete Datenstruktur zurück.
|
| 13 |
"""
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.run_silent_cogitation_seismic', return_value=mock_deltas)
|
| 17 |
|
| 18 |
-
# Mocke den Gradio Progress-Callback
|
| 19 |
mock_progress = mocker.MagicMock()
|
| 20 |
|
| 21 |
-
# Führe die zu testende Funktion aus
|
| 22 |
results = run_seismic_analysis(
|
| 23 |
model_id="mock_model",
|
| 24 |
prompt_type="test_prompt",
|
| 25 |
seed=42,
|
| 26 |
-
num_steps=
|
|
|
|
|
|
|
| 27 |
progress_callback=mock_progress
|
| 28 |
)
|
| 29 |
|
| 30 |
-
# ASSERT:
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
assert
|
| 34 |
|
| 35 |
-
|
| 36 |
-
assert stats["mean_delta"] == pytest.approx(
|
| 37 |
-
assert stats["std_delta"] == pytest.approx(np.std(mock_deltas))
|
| 38 |
-
assert stats["max_delta"] == pytest.approx(max(mock_deltas))
|
| 39 |
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
-
# ASSERT:
|
| 43 |
-
assert
|
|
|
|
| 1 |
import numpy as np
|
| 2 |
import pytest
|
| 3 |
+
import torch
|
| 4 |
from types import SimpleNamespace
|
| 5 |
|
| 6 |
from cognitive_mapping_probe.orchestrator_seismograph import run_seismic_analysis
|
| 7 |
|
| 8 |
+
def test_seismic_analysis_orchestrator_no_injection(mocker, mock_llm):
|
| 9 |
"""
|
| 10 |
+
Testet den Orchestrator im Baseline-Modus (ohne Injektion).
|
|
|
|
|
|
|
|
|
|
| 11 |
"""
|
| 12 |
+
mock_deltas = [1.0, 2.0, 3.0]
|
| 13 |
+
mock_run_seismic = mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.run_silent_cogitation_seismic', return_value=mock_deltas)
|
|
|
|
| 14 |
|
|
|
|
| 15 |
mock_progress = mocker.MagicMock()
|
| 16 |
|
|
|
|
| 17 |
results = run_seismic_analysis(
|
| 18 |
model_id="mock_model",
|
| 19 |
prompt_type="test_prompt",
|
| 20 |
seed=42,
|
| 21 |
+
num_steps=3,
|
| 22 |
+
concept_to_inject="", # Kein Konzept
|
| 23 |
+
injection_strength=0.0,
|
| 24 |
progress_callback=mock_progress
|
| 25 |
)
|
| 26 |
|
| 27 |
+
# ASSERT: `run_silent_cogitation_seismic` wurde mit `injection_vector=None` aufgerufen
|
| 28 |
+
mock_run_seismic.assert_called_once()
|
| 29 |
+
call_args, call_kwargs = mock_run_seismic.call_args
|
| 30 |
+
assert call_kwargs['injection_vector'] is None
|
| 31 |
|
| 32 |
+
# ASSERT: Die Statistiken sind korrekt
|
| 33 |
+
assert results["stats"]["mean_delta"] == pytest.approx(2.0)
|
|
|
|
|
|
|
| 34 |
|
| 35 |
+
def test_seismic_analysis_orchestrator_with_injection(mocker, mock_llm):
|
| 36 |
+
"""
|
| 37 |
+
Testet den Orchestrator mit aktivierter Konzeptinjektion.
|
| 38 |
+
"""
|
| 39 |
+
mock_deltas = [5.0, 6.0, 7.0]
|
| 40 |
+
mock_run_seismic = mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.run_silent_cogitation_seismic', return_value=mock_deltas)
|
| 41 |
+
|
| 42 |
+
# Der `mock_llm` Fixture patcht bereits `get_concept_vector`
|
| 43 |
+
mock_get_concept_vector = mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_concept_vector')
|
| 44 |
+
|
| 45 |
+
mock_progress = mocker.MagicMock()
|
| 46 |
+
|
| 47 |
+
results = run_seismic_analysis(
|
| 48 |
+
model_id="mock_model",
|
| 49 |
+
prompt_type="test_prompt",
|
| 50 |
+
seed=42,
|
| 51 |
+
num_steps=3,
|
| 52 |
+
concept_to_inject="test_concept", # Konzept wird übergeben
|
| 53 |
+
injection_strength=1.5,
|
| 54 |
+
progress_callback=mock_progress
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
# ASSERT: `get_concept_vector` wurde aufgerufen
|
| 58 |
+
mock_get_concept_vector.assert_called_once_with(mocker.ANY, "test_concept")
|
| 59 |
+
|
| 60 |
+
# ASSERT: `run_silent_cogitation_seismic` wurde mit einem Vektor und Stärke aufgerufen
|
| 61 |
+
mock_run_seismic.assert_called_once()
|
| 62 |
+
call_args, call_kwargs = mock_run_seismic.call_args
|
| 63 |
+
assert call_kwargs['injection_vector'] is not None
|
| 64 |
+
assert call_kwargs['injection_strength'] == 1.5
|
| 65 |
|
| 66 |
+
# ASSERT: Die Statistiken sind korrekt
|
| 67 |
+
assert results["stats"]["mean_delta"] == pytest.approx(6.0)
|