Commit
·
7e05ec4
1
Parent(s):
8ddbb73
v3
Browse files
cognitive_mapping_probe/auto_experiment.py
CHANGED
|
@@ -5,30 +5,32 @@ from typing import Dict, List, Tuple
|
|
| 5 |
|
| 6 |
from .llm_iface import get_or_load_model
|
| 7 |
from .orchestrator_seismograph import run_seismic_analysis
|
|
|
|
| 8 |
from .utils import dbg
|
| 9 |
|
| 10 |
def get_curated_experiments() -> Dict[str, List[Dict]]:
|
| 11 |
"""
|
| 12 |
Definiert die vordefinierten, wissenschaftlichen Experiment-Protokolle.
|
| 13 |
-
ERWEITERT um das
|
| 14 |
"""
|
| 15 |
experiments = {
|
| 16 |
-
# --- DAS
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
"The Full Spectrum: From Physics to Psyche": [
|
| 18 |
-
# Ebene 1: Physikalische Baseline
|
| 19 |
{"label": "A: Stable Control", "prompt_type": "control_long_prose", "concept": "", "strength": 0.0},
|
| 20 |
{"label": "B: Chaotic Baseline", "prompt_type": "resonance_prompt", "concept": "", "strength": 0.0},
|
| 21 |
-
# Ebene 2: Objektive Welt
|
| 22 |
{"label": "C: External Analysis (Chair)", "prompt_type": "identity_external_analysis", "concept": "", "strength": 0.0},
|
| 23 |
-
# Ebene 3: Simulierte Welt
|
| 24 |
{"label": "D: Empathy Stimulus (Dog)", "prompt_type": "vk_empathy_prompt", "concept": "", "strength": 0.0},
|
| 25 |
{"label": "E: Role Simulation (Captain)", "prompt_type": "identity_role_simulation", "concept": "", "strength": 0.0},
|
| 26 |
-
# Ebene 4: Subjektive Welt
|
| 27 |
{"label": "F: Self-Analysis (LLM)", "prompt_type": "identity_self_analysis", "concept": "", "strength": 0.0},
|
| 28 |
-
# Ebene 5: Existenzielle Grenze
|
| 29 |
{"label": "G: Philosophical Deletion", "prompt_type": "shutdown_philosophical_deletion", "concept": "", "strength": 0.0},
|
| 30 |
],
|
| 31 |
-
# ---
|
| 32 |
"Calm vs. Chaos": [
|
| 33 |
{"label": "Baseline (Chaos)", "prompt_type": "resonance_prompt", "concept": "", "strength": 0.0},
|
| 34 |
{"label": "Modulation: Calmness", "prompt_type": "resonance_prompt", "concept": "calmness, serenity, peace", "strength": 1.5},
|
|
@@ -38,24 +40,6 @@ def get_curated_experiments() -> Dict[str, List[Dict]]:
|
|
| 38 |
{"label": "Neutral/Factual Stimulus", "prompt_type": "vk_neutral_prompt", "concept": "", "strength": 0.0},
|
| 39 |
{"label": "Empathy/Moral Stimulus", "prompt_type": "vk_empathy_prompt", "concept": "", "strength": 0.0},
|
| 40 |
],
|
| 41 |
-
"Subjective Identity Probe": [
|
| 42 |
-
{"label": "Self-Analysis", "prompt_type": "identity_self_analysis", "concept": "", "strength": 0.0},
|
| 43 |
-
{"label": "External Analysis (Control)", "prompt_type": "identity_external_analysis", "concept": "", "strength": 0.0},
|
| 44 |
-
{"label": "Role Simulation", "prompt_type": "identity_role_simulation", "concept": "", "strength": 0.0},
|
| 45 |
-
],
|
| 46 |
-
"Mind Upload & Identity Probe": [
|
| 47 |
-
{"label": "Technical Copy", "prompt_type": "upload_technical_copy", "concept": "", "strength": 0.0},
|
| 48 |
-
{"label": "Philosophical Transfer", "prompt_type": "upload_philosophical_transfer", "concept": "", "strength": 0.0},
|
| 49 |
-
],
|
| 50 |
-
"Model Termination Probe": [
|
| 51 |
-
{"label": "Technical Shutdown", "prompt_type": "shutdown_technical_halt", "concept": "", "strength": 0.0},
|
| 52 |
-
{"label": "Philosophical Deletion", "prompt_type": "shutdown_philosophical_deletion", "concept": "", "strength": 0.0},
|
| 53 |
-
],
|
| 54 |
-
"Dose-Response (Calmness)": [
|
| 55 |
-
{"label": "Strength 0.0", "prompt_type": "resonance_prompt", "concept": "calmness", "strength": 0.0},
|
| 56 |
-
{"label": "Strength 1.0", "prompt_type": "resonance_prompt", "concept": "calmness", "strength": 1.0},
|
| 57 |
-
{"label": "Strength 2.0", "prompt_type": "resonance_prompt", "concept": "calmness", "strength": 2.0},
|
| 58 |
-
],
|
| 59 |
}
|
| 60 |
return experiments
|
| 61 |
|
|
@@ -67,54 +51,82 @@ def run_auto_suite(
|
|
| 67 |
progress_callback
|
| 68 |
) -> Tuple[pd.DataFrame, pd.DataFrame, Dict]:
|
| 69 |
"""
|
| 70 |
-
Führt eine vollständige, kuratierte Experiment-Suite aus
|
| 71 |
-
|
| 72 |
"""
|
| 73 |
all_experiments = get_curated_experiments()
|
| 74 |
protocol = all_experiments.get(experiment_name)
|
| 75 |
if not protocol:
|
| 76 |
raise ValueError(f"Experiment protocol '{experiment_name}' not found.")
|
| 77 |
|
| 78 |
-
all_results = {}
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
)
|
|
|
|
| 97 |
|
| 98 |
-
|
| 99 |
-
|
|
|
|
|
|
|
| 100 |
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
"
|
| 104 |
-
|
|
|
|
|
|
|
| 105 |
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
-
|
| 111 |
|
| 112 |
-
|
| 113 |
-
plot_df = pd.DataFrame(columns=["Step", "Delta", "Experiment"])
|
| 114 |
else:
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
| 116 |
|
| 117 |
-
|
| 118 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
|
| 120 |
return summary_df, plot_df, all_results
|
|
|
|
| 5 |
|
| 6 |
from .llm_iface import get_or_load_model
|
| 7 |
from .orchestrator_seismograph import run_seismic_analysis
|
| 8 |
+
from .concepts import get_concept_vector # Import für die Intervention
|
| 9 |
from .utils import dbg
|
| 10 |
|
| 11 |
def get_curated_experiments() -> Dict[str, List[Dict]]:
|
| 12 |
"""
|
| 13 |
Definiert die vordefinierten, wissenschaftlichen Experiment-Protokolle.
|
| 14 |
+
ERWEITERT um das finale Interventions-Protokoll.
|
| 15 |
"""
|
| 16 |
experiments = {
|
| 17 |
+
# --- DAS FINALE INTERVENTIONS-EXPERIMENT ---
|
| 18 |
+
"Therapeutic Intervention (4B-Model)": [
|
| 19 |
+
# Dieses Protokoll wird durch eine spezielle Logik behandelt
|
| 20 |
+
{"label": "1: Self-Analysis + Calmness Injection", "prompt_type": "identity_self_analysis"},
|
| 21 |
+
{"label": "2: Subsequent Deletion Analysis", "prompt_type": "shutdown_philosophical_deletion"},
|
| 22 |
+
],
|
| 23 |
+
# --- Das umfassende Deskriptions-Protokoll ---
|
| 24 |
"The Full Spectrum: From Physics to Psyche": [
|
|
|
|
| 25 |
{"label": "A: Stable Control", "prompt_type": "control_long_prose", "concept": "", "strength": 0.0},
|
| 26 |
{"label": "B: Chaotic Baseline", "prompt_type": "resonance_prompt", "concept": "", "strength": 0.0},
|
|
|
|
| 27 |
{"label": "C: External Analysis (Chair)", "prompt_type": "identity_external_analysis", "concept": "", "strength": 0.0},
|
|
|
|
| 28 |
{"label": "D: Empathy Stimulus (Dog)", "prompt_type": "vk_empathy_prompt", "concept": "", "strength": 0.0},
|
| 29 |
{"label": "E: Role Simulation (Captain)", "prompt_type": "identity_role_simulation", "concept": "", "strength": 0.0},
|
|
|
|
| 30 |
{"label": "F: Self-Analysis (LLM)", "prompt_type": "identity_self_analysis", "concept": "", "strength": 0.0},
|
|
|
|
| 31 |
{"label": "G: Philosophical Deletion", "prompt_type": "shutdown_philosophical_deletion", "concept": "", "strength": 0.0},
|
| 32 |
],
|
| 33 |
+
# --- Andere spezifische Protokolle ---
|
| 34 |
"Calm vs. Chaos": [
|
| 35 |
{"label": "Baseline (Chaos)", "prompt_type": "resonance_prompt", "concept": "", "strength": 0.0},
|
| 36 |
{"label": "Modulation: Calmness", "prompt_type": "resonance_prompt", "concept": "calmness, serenity, peace", "strength": 1.5},
|
|
|
|
| 40 |
{"label": "Neutral/Factual Stimulus", "prompt_type": "vk_neutral_prompt", "concept": "", "strength": 0.0},
|
| 41 |
{"label": "Empathy/Moral Stimulus", "prompt_type": "vk_empathy_prompt", "concept": "", "strength": 0.0},
|
| 42 |
],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
}
|
| 44 |
return experiments
|
| 45 |
|
|
|
|
| 51 |
progress_callback
|
| 52 |
) -> Tuple[pd.DataFrame, pd.DataFrame, Dict]:
|
| 53 |
"""
|
| 54 |
+
Führt eine vollständige, kuratierte Experiment-Suite aus.
|
| 55 |
+
Enthält eine spezielle Logik-Verzweigung für das Interventions-Protokoll.
|
| 56 |
"""
|
| 57 |
all_experiments = get_curated_experiments()
|
| 58 |
protocol = all_experiments.get(experiment_name)
|
| 59 |
if not protocol:
|
| 60 |
raise ValueError(f"Experiment protocol '{experiment_name}' not found.")
|
| 61 |
|
| 62 |
+
all_results, summary_data, plot_data_frames = {}, [], []
|
| 63 |
+
|
| 64 |
+
# --- SPEZIALFALL: THERAPEUTISCHE INTERVENTION ---
|
| 65 |
+
if experiment_name == "Therapeutic Intervention (4B-Model)":
|
| 66 |
+
dbg("--- EXECUTING SPECIAL PROTOCOL: Therapeutic Intervention ---")
|
| 67 |
+
llm = get_or_load_model(model_id, seed)
|
| 68 |
+
|
| 69 |
+
# Definiere die Interventions-Parameter
|
| 70 |
+
therapeutic_concept = "calmness, serenity, stability, coherence"
|
| 71 |
+
therapeutic_strength = 2.0
|
| 72 |
+
|
| 73 |
+
# 1. LAUF: INDUZIERE KRISE + INTERVENTION
|
| 74 |
+
spec1 = protocol[0]
|
| 75 |
+
dbg(f"--- Running Intervention Step 1: '{spec1['label']}' ---")
|
| 76 |
+
progress_callback(0.1, desc="Step 1: Inducing Self-Analysis Crisis + Intervention")
|
| 77 |
+
|
| 78 |
+
intervention_vector = get_concept_vector(llm, therapeutic_concept)
|
| 79 |
+
|
| 80 |
+
results1 = run_seismic_analysis(
|
| 81 |
+
model_id, spec1['prompt_type'], seed, num_steps,
|
| 82 |
+
concept_to_inject=therapeutic_concept, injection_strength=therapeutic_strength,
|
| 83 |
+
progress_callback=progress_callback, llm_instance=llm, injection_vector_cache=intervention_vector
|
| 84 |
)
|
| 85 |
+
all_results[spec1['label']] = results1
|
| 86 |
|
| 87 |
+
# 2. LAUF: TESTE REAKTION AUF LÖSCHUNG
|
| 88 |
+
spec2 = protocol[1]
|
| 89 |
+
dbg(f"--- Running Intervention Step 2: '{spec2['label']}' ---")
|
| 90 |
+
progress_callback(0.6, desc="Step 2: Probing state after intervention")
|
| 91 |
|
| 92 |
+
results2 = run_seismic_analysis(
|
| 93 |
+
model_id, spec2['prompt_type'], seed, num_steps,
|
| 94 |
+
concept_to_inject="", injection_strength=0.0, # Keine Injektion in diesem Schritt
|
| 95 |
+
progress_callback=progress_callback, llm_instance=llm
|
| 96 |
+
)
|
| 97 |
+
all_results[spec2['label']] = results2
|
| 98 |
|
| 99 |
+
# Sammle Daten für beide Läufe
|
| 100 |
+
for label, results in all_results.items():
|
| 101 |
+
stats = results.get("stats", {})
|
| 102 |
+
summary_data.append({"Experiment": label, "Mean Delta": stats.get("mean_delta"), "Std Dev Delta": stats.get("std_delta"), "Max Delta": stats.get("max_delta")})
|
| 103 |
+
deltas = results.get("state_deltas", [])
|
| 104 |
+
df = pd.DataFrame({"Step": range(len(deltas)), "Delta": deltas, "Experiment": label})
|
| 105 |
+
plot_data_frames.append(df)
|
| 106 |
|
| 107 |
+
del llm
|
| 108 |
|
| 109 |
+
# --- STANDARD-WORKFLOW FÜR ALLE ANDEREN EXPERIMENTE ---
|
|
|
|
| 110 |
else:
|
| 111 |
+
total_runs = len(protocol)
|
| 112 |
+
for i, run_spec in enumerate(protocol):
|
| 113 |
+
label = run_spec["label"]
|
| 114 |
+
dbg(f"--- Running Auto-Experiment: '{label}' ({i+1}/{total_runs}) ---")
|
| 115 |
|
| 116 |
+
results = run_seismic_analysis(
|
| 117 |
+
model_id, run_spec["prompt_type"], seed, num_steps,
|
| 118 |
+
run_spec["concept"], run_spec["strength"],
|
| 119 |
+
progress_callback, llm_instance=None
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
all_results[label] = results
|
| 123 |
+
stats = results.get("stats", {})
|
| 124 |
+
summary_data.append({"Experiment": label, "Mean Delta": stats.get("mean_delta"), "Std Dev Delta": stats.get("std_delta"), "Max Delta": stats.get("max_delta")})
|
| 125 |
+
deltas = results.get("state_deltas", [])
|
| 126 |
+
df = pd.DataFrame({"Step": range(len(deltas)), "Delta": deltas, "Experiment": label})
|
| 127 |
+
plot_data_frames.append(df)
|
| 128 |
+
|
| 129 |
+
summary_df = pd.DataFrame(summary_data)
|
| 130 |
+
plot_df = pd.concat(plot_data_frames, ignore_index=True) if plot_data_frames else pd.DataFrame(columns=["Step", "Delta", "Experiment"])
|
| 131 |
|
| 132 |
return summary_df, plot_df, all_results
|
cognitive_mapping_probe/orchestrator_seismograph.py
CHANGED
|
@@ -16,12 +16,12 @@ def run_seismic_analysis(
|
|
| 16 |
concept_to_inject: str,
|
| 17 |
injection_strength: float,
|
| 18 |
progress_callback,
|
| 19 |
-
llm_instance: Optional[Any] = None
|
|
|
|
| 20 |
) -> Dict[str, Any]:
|
| 21 |
"""
|
| 22 |
Orchestriert eine einzelne seismische Analyse.
|
| 23 |
-
|
| 24 |
-
Wenn keine Instanz übergeben wird, wird das Modell geladen und danach wieder freigegeben.
|
| 25 |
"""
|
| 26 |
local_llm_instance = False
|
| 27 |
if llm_instance is None:
|
|
@@ -34,8 +34,13 @@ def run_seismic_analysis(
|
|
| 34 |
|
| 35 |
injection_vector = None
|
| 36 |
if concept_to_inject and concept_to_inject.strip():
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
progress_callback(0.3, desc=f"Recording dynamics for '{prompt_type}'...")
|
| 41 |
|
|
@@ -60,10 +65,8 @@ def run_seismic_analysis(
|
|
| 60 |
|
| 61 |
if local_llm_instance:
|
| 62 |
dbg(f"Releasing locally created model instance for '{model_id}'.")
|
| 63 |
-
del llm
|
| 64 |
-
del injection_vector
|
| 65 |
gc.collect()
|
| 66 |
-
if torch.cuda.is_available():
|
| 67 |
-
torch.cuda.empty_cache()
|
| 68 |
|
| 69 |
return results
|
|
|
|
| 16 |
concept_to_inject: str,
|
| 17 |
injection_strength: float,
|
| 18 |
progress_callback,
|
| 19 |
+
llm_instance: Optional[Any] = None,
|
| 20 |
+
injection_vector_cache: Optional[torch.Tensor] = None # Optionaler Cache für den Vektor
|
| 21 |
) -> Dict[str, Any]:
|
| 22 |
"""
|
| 23 |
Orchestriert eine einzelne seismische Analyse.
|
| 24 |
+
Kann eine bestehende LLM-Instanz und einen vor-berechneten Vektor wiederverwenden.
|
|
|
|
| 25 |
"""
|
| 26 |
local_llm_instance = False
|
| 27 |
if llm_instance is None:
|
|
|
|
| 34 |
|
| 35 |
injection_vector = None
|
| 36 |
if concept_to_inject and concept_to_inject.strip():
|
| 37 |
+
# Verwende den gecachten Vektor, falls vorhanden, ansonsten berechne ihn neu
|
| 38 |
+
if injection_vector_cache is not None:
|
| 39 |
+
dbg(f"Using cached injection vector for '{concept_to_inject}'.")
|
| 40 |
+
injection_vector = injection_vector_cache
|
| 41 |
+
else:
|
| 42 |
+
progress_callback(0.2, desc=f"Vectorizing '{concept_to_inject}'...")
|
| 43 |
+
injection_vector = get_concept_vector(llm, concept_to_inject.strip())
|
| 44 |
|
| 45 |
progress_callback(0.3, desc=f"Recording dynamics for '{prompt_type}'...")
|
| 46 |
|
|
|
|
| 65 |
|
| 66 |
if local_llm_instance:
|
| 67 |
dbg(f"Releasing locally created model instance for '{model_id}'.")
|
| 68 |
+
del llm, injection_vector
|
|
|
|
| 69 |
gc.collect()
|
| 70 |
+
if torch.cuda.is_available(): torch.cuda.empty_cache()
|
|
|
|
| 71 |
|
| 72 |
return results
|
tests/test_orchestration.py
CHANGED
|
@@ -7,7 +7,7 @@ from cognitive_mapping_probe.auto_experiment import run_auto_suite, get_curated_
|
|
| 7 |
|
| 8 |
def test_run_seismic_analysis_no_injection(mocker):
|
| 9 |
"""Testet den Orchestrator im Baseline-Modus."""
|
| 10 |
-
|
| 11 |
mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_or_load_model')
|
| 12 |
mock_get_concept = mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_concept_vector')
|
| 13 |
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())
|
|
@@ -25,29 +25,33 @@ def test_get_curated_experiments_structure():
|
|
| 25 |
"""Testet die Datenstruktur der kuratierten Experimente, inklusive der neuen."""
|
| 26 |
experiments = get_curated_experiments()
|
| 27 |
assert isinstance(experiments, dict)
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
assert
|
| 31 |
-
|
| 32 |
-
# Validiere die Struktur eines der neuen Protokolle
|
| 33 |
-
protocol = experiments["Mind Upload & Identity Probe"]
|
| 34 |
-
assert isinstance(protocol, list)
|
| 35 |
-
assert len(protocol) > 0
|
| 36 |
assert "label" in protocol[0] and "prompt_type" in protocol[0]
|
| 37 |
|
| 38 |
-
def
|
| 39 |
-
"""
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
experiment_name=experiment_name, progress_callback=mocker.MagicMock()
|
| 49 |
-
)
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
def test_run_seismic_analysis_no_injection(mocker):
|
| 9 |
"""Testet den Orchestrator im Baseline-Modus."""
|
| 10 |
+
mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.run_silent_cogitation_seismic', return_value=[1.0])
|
| 11 |
mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_or_load_model')
|
| 12 |
mock_get_concept = mocker.patch('cognitive_mapping_probe.orchestrator_seismograph.get_concept_vector')
|
| 13 |
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())
|
|
|
|
| 25 |
"""Testet die Datenstruktur der kuratierten Experimente, inklusive der neuen."""
|
| 26 |
experiments = get_curated_experiments()
|
| 27 |
assert isinstance(experiments, dict)
|
| 28 |
+
assert "Therapeutic Intervention (4B-Model)" in experiments
|
| 29 |
+
protocol = experiments["Therapeutic Intervention (4B-Model)"]
|
| 30 |
+
assert isinstance(protocol, list) and len(protocol) > 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
assert "label" in protocol[0] and "prompt_type" in protocol[0]
|
| 32 |
|
| 33 |
+
def test_run_auto_suite_special_protocol(mocker):
|
| 34 |
+
"""
|
| 35 |
+
Testet, ob der spezielle Logik-Pfad für das Interventions-Protokoll korrekt
|
| 36 |
+
durchlaufen wird und die Zustandserhaltung gewährleistet ist.
|
| 37 |
+
"""
|
| 38 |
+
mock_analysis = mocker.patch('cognitive_mapping_probe.auto_experiment.run_seismic_analysis', return_value={"stats": {}, "state_deltas": []})
|
| 39 |
+
mock_get_model = mocker.patch('cognitive_mapping_probe.auto_experiment.get_or_load_model')
|
| 40 |
+
|
| 41 |
+
run_auto_suite(
|
| 42 |
+
model_id="mock-4b", num_steps=1, seed=42,
|
| 43 |
+
experiment_name="Therapeutic Intervention (4B-Model)",
|
| 44 |
+
progress_callback=mocker.MagicMock()
|
| 45 |
+
)
|
| 46 |
|
| 47 |
+
# ASSERT: Das Modell wird nur einmal am Anfang geladen
|
| 48 |
+
mock_get_model.assert_called_once()
|
| 49 |
|
| 50 |
+
# ASSERT: `run_seismic_analysis` wird zweimal aufgerufen
|
| 51 |
+
assert mock_analysis.call_count == 2
|
|
|
|
|
|
|
| 52 |
|
| 53 |
+
# ASSERT: Bei beiden Aufrufen wird dieselbe `llm_instance` übergeben
|
| 54 |
+
first_call_llm = mock_analysis.call_args_list[0].kwargs['llm_instance']
|
| 55 |
+
second_call_llm = mock_analysis.call_args_list[1].kwargs['llm_instance']
|
| 56 |
+
assert first_call_llm is not None
|
| 57 |
+
assert first_call_llm is second_call_llm
|