Add Ikario Architecture v2 - Phases 1-8 complete
Implements the processual architecture based on Whitehead's Process Philosophy and Peirce's Semiotics. Core paradigm: "L'espace latent pense. Le LLM traduit." (The latent space thinks. The LLM translates.) Phase 1-4: Core semiotic cycle - StateTensor 8x1024 (8 Peircean dimensions) - Dissonance computation with hard negatives - Fixation via 4 Peircean methods (Tenacity, Authority, A Priori, Science) - LatentEngine orchestrating the full cycle Phase 5: StateToLanguage - LLM as pure translator (zero-reasoning, T=0) - Projection on interpretable directions - Reasoning markers detection (Amendment #4) Phase 6: Vigilance - x_ref (David) as guard-rail, NOT attractor - Drift detection per dimension and globally - Alerts: ok, warning, critical Phase 7: Autonomous Daemon - Two modes: CONVERSATION (always verbalize), AUTONOMOUS (~1000 cycles/day) - Amendment #5: 50% probability on unresolved impacts - TriggerGenerator with weighted random selection Phase 8: Integration & Metrics - ProcessMetrics for daily/weekly reports - Health status monitoring - Integration tests validating all modules 297 tests passing, version 0.7.0 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
436
ikario_processual/tests/test_integration_v2.py
Normal file
436
ikario_processual/tests/test_integration_v2.py
Normal file
@@ -0,0 +1,436 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tests d'intégration Phase 8 - Architecture v2.
|
||||
|
||||
Tests simplifiés pour valider l'intégration entre les modules.
|
||||
Ces tests utilisent l'API réelle des modules implémentés.
|
||||
|
||||
Exécuter: pytest ikario_processual/tests/test_integration_v2.py -v
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import numpy as np
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
||||
|
||||
from ikario_processual.state_tensor import StateTensor, DIMENSION_NAMES, EMBEDDING_DIM
|
||||
from ikario_processual.dissonance import DissonanceResult, Impact, compute_dissonance
|
||||
from ikario_processual.fixation import FixationResult, compute_delta, apply_delta
|
||||
from ikario_processual.vigilance import (
|
||||
VigilanceSystem,
|
||||
VigilanceConfig,
|
||||
VigilanceAlert,
|
||||
create_vigilance_system,
|
||||
)
|
||||
from ikario_processual.state_to_language import (
|
||||
StateToLanguage,
|
||||
TranslationResult,
|
||||
ProjectionDirection,
|
||||
REASONING_MARKERS,
|
||||
)
|
||||
from ikario_processual.daemon import (
|
||||
IkarioDaemon,
|
||||
DaemonConfig,
|
||||
DaemonMode,
|
||||
TriggerType,
|
||||
Trigger,
|
||||
TriggerGenerator,
|
||||
create_daemon,
|
||||
)
|
||||
from ikario_processual.metrics import (
|
||||
ProcessMetrics,
|
||||
create_metrics,
|
||||
)
|
||||
|
||||
|
||||
def create_random_tensor(state_id: int = 0, seed: int = None) -> StateTensor:
|
||||
"""Crée un tenseur avec des vecteurs aléatoires normalisés."""
|
||||
if seed is not None:
|
||||
np.random.seed(seed)
|
||||
|
||||
tensor = StateTensor(
|
||||
state_id=state_id,
|
||||
timestamp=datetime.now().isoformat(),
|
||||
)
|
||||
for dim_name in DIMENSION_NAMES:
|
||||
v = np.random.randn(EMBEDDING_DIM)
|
||||
v = v / np.linalg.norm(v)
|
||||
setattr(tensor, dim_name, v)
|
||||
return tensor
|
||||
|
||||
|
||||
def create_mock_embedding_model():
|
||||
"""Crée un mock du modèle d'embedding."""
|
||||
mock = MagicMock()
|
||||
|
||||
def mock_encode(texts):
|
||||
np.random.seed(hash(str(texts)) % (2**32))
|
||||
embeddings = np.random.randn(len(texts), EMBEDDING_DIM)
|
||||
norms = np.linalg.norm(embeddings, axis=1, keepdims=True)
|
||||
return embeddings / norms
|
||||
|
||||
mock.encode = mock_encode
|
||||
return mock
|
||||
|
||||
|
||||
class TestVigilanceIntegration:
|
||||
"""Tests d'intégration du système de vigilance."""
|
||||
|
||||
def test_vigilance_with_state_tensor(self):
|
||||
"""Test: vigilance fonctionne avec StateTensor."""
|
||||
x_ref = create_random_tensor(state_id=-1, seed=42)
|
||||
vigilance = VigilanceSystem(x_ref=x_ref)
|
||||
|
||||
# État identique = pas de drift
|
||||
alert = vigilance.check_drift(x_ref)
|
||||
assert alert.level == "ok"
|
||||
|
||||
def test_vigilance_detects_drift(self):
|
||||
"""Test: vigilance détecte la dérive."""
|
||||
x_ref = create_random_tensor(state_id=-1, seed=42)
|
||||
config = VigilanceConfig(
|
||||
threshold_cumulative=0.0001,
|
||||
threshold_per_cycle=0.00001,
|
||||
)
|
||||
vigilance = VigilanceSystem(x_ref=x_ref, config=config)
|
||||
|
||||
# Premier check
|
||||
vigilance.check_drift(x_ref)
|
||||
|
||||
# État différent = dérive
|
||||
X_different = create_random_tensor(state_id=1, seed=999)
|
||||
alert = vigilance.check_drift(X_different)
|
||||
|
||||
assert alert.level in ("warning", "critical")
|
||||
|
||||
def test_vigilance_identifies_dimensions(self):
|
||||
"""Test: vigilance identifie les dimensions en dérive."""
|
||||
x_ref = create_random_tensor(state_id=-1, seed=42)
|
||||
vigilance = VigilanceSystem(x_ref=x_ref)
|
||||
|
||||
# Inverser une dimension
|
||||
X_modified = x_ref.copy()
|
||||
X_modified.state_id = 1
|
||||
X_modified.valeurs = -x_ref.valeurs
|
||||
|
||||
alert = vigilance.check_drift(X_modified)
|
||||
assert 'valeurs' in alert.top_drifting_dimensions
|
||||
|
||||
def test_vigilance_cumulative_drift(self):
|
||||
"""Test: dérive cumulative augmente."""
|
||||
x_ref = create_random_tensor(state_id=-1, seed=42)
|
||||
vigilance = VigilanceSystem(x_ref=x_ref)
|
||||
|
||||
# Plusieurs checks
|
||||
for i in range(5):
|
||||
X = create_random_tensor(state_id=i, seed=i + 100)
|
||||
vigilance.check_drift(X)
|
||||
|
||||
assert vigilance.cumulative_drift > 0
|
||||
assert len(vigilance.history) == 5
|
||||
|
||||
|
||||
class TestStateToLanguageIntegration:
|
||||
"""Tests d'intégration de StateToLanguage."""
|
||||
|
||||
def test_projection_on_directions(self):
|
||||
"""Test: projection sur les directions."""
|
||||
X = create_random_tensor(state_id=5, seed=42)
|
||||
|
||||
# Créer direction avec la bonne signature
|
||||
direction_vec = np.random.randn(EMBEDDING_DIM)
|
||||
direction_vec = direction_vec / np.linalg.norm(direction_vec)
|
||||
|
||||
direction = ProjectionDirection(
|
||||
name="test_dir",
|
||||
category="epistemic",
|
||||
pole_positive="positif",
|
||||
pole_negative="négatif",
|
||||
description="Direction de test",
|
||||
vector=direction_vec,
|
||||
)
|
||||
|
||||
translator = StateToLanguage(directions=[direction])
|
||||
projections = translator.project_state(X)
|
||||
|
||||
# Projection existe pour la catégorie epistemic
|
||||
assert 'epistemic' in projections
|
||||
assert 'test_dir' in projections['epistemic']
|
||||
|
||||
def test_translator_async_translate(self):
|
||||
"""Test: traduction async avec mock client."""
|
||||
|
||||
def run_test():
|
||||
async def async_test():
|
||||
X = create_random_tensor(state_id=5, seed=42)
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.messages.create = AsyncMock(return_value=MagicMock(
|
||||
content=[MagicMock(text="État de curiosité intense.")]
|
||||
))
|
||||
|
||||
translator = StateToLanguage(
|
||||
directions=[],
|
||||
anthropic_client=mock_client,
|
||||
)
|
||||
|
||||
result = await translator.translate(X)
|
||||
|
||||
assert result is not None
|
||||
assert isinstance(result, TranslationResult)
|
||||
assert len(result.text) > 0
|
||||
|
||||
asyncio.run(async_test())
|
||||
|
||||
run_test()
|
||||
|
||||
def test_reasoning_markers_defined(self):
|
||||
"""Test: marqueurs de raisonnement définis."""
|
||||
assert len(REASONING_MARKERS) > 0
|
||||
assert any("pense" in m.lower() for m in REASONING_MARKERS)
|
||||
|
||||
|
||||
class TestDissonanceFixationIntegration:
|
||||
"""Tests d'intégration dissonance + fixation."""
|
||||
|
||||
def test_dissonance_on_tensor(self):
|
||||
"""Test: compute_dissonance fonctionne."""
|
||||
X = create_random_tensor(state_id=0, seed=42)
|
||||
mock_model = create_mock_embedding_model()
|
||||
|
||||
e_input = mock_model.encode(["Test input"])[0]
|
||||
|
||||
result = compute_dissonance(
|
||||
e_input=e_input,
|
||||
X_t=X,
|
||||
)
|
||||
|
||||
assert isinstance(result, DissonanceResult)
|
||||
assert len(result.dissonances_by_dimension) == 8
|
||||
|
||||
def test_fixation_applies_delta(self):
|
||||
"""Test: fixation applique le delta."""
|
||||
X = create_random_tensor(state_id=0, seed=42)
|
||||
X_before = X.to_flat().copy()
|
||||
|
||||
# Créer un delta
|
||||
delta = np.random.randn(EMBEDDING_DIM) * 0.01
|
||||
|
||||
# Appliquer sur une dimension
|
||||
X_new = apply_delta(
|
||||
X_t=X,
|
||||
delta=delta,
|
||||
target_dim="firstness",
|
||||
)
|
||||
|
||||
X_after = X_new.to_flat()
|
||||
|
||||
# L'état a changé
|
||||
assert not np.allclose(X_before, X_after)
|
||||
|
||||
|
||||
class TestDaemonComponents:
|
||||
"""Tests des composants du daemon."""
|
||||
|
||||
def test_trigger_creation(self):
|
||||
"""Test: création de triggers."""
|
||||
trigger = Trigger(
|
||||
type=TriggerType.USER,
|
||||
content="Test message",
|
||||
metadata={"source": "test"},
|
||||
)
|
||||
|
||||
assert trigger.type == TriggerType.USER
|
||||
assert trigger.content == "Test message"
|
||||
assert trigger.metadata["source"] == "test"
|
||||
|
||||
def test_daemon_config_validation(self):
|
||||
"""Test: validation de config."""
|
||||
config = DaemonConfig()
|
||||
|
||||
total = (
|
||||
config.prob_unresolved_impact +
|
||||
config.prob_corpus +
|
||||
config.prob_rumination_free
|
||||
)
|
||||
assert np.isclose(total, 1.0)
|
||||
assert config.validate() == True
|
||||
|
||||
def test_daemon_mode_enum(self):
|
||||
"""Test: modes du daemon."""
|
||||
assert DaemonMode.CONVERSATION.value == "conversation"
|
||||
assert DaemonMode.AUTONOMOUS.value == "autonomous"
|
||||
|
||||
def test_trigger_types(self):
|
||||
"""Test: types de triggers."""
|
||||
assert TriggerType.USER.value == "user"
|
||||
assert TriggerType.VEILLE.value == "veille"
|
||||
assert TriggerType.CORPUS.value == "corpus"
|
||||
assert TriggerType.RUMINATION_FREE.value == "rumination_free"
|
||||
|
||||
|
||||
class TestMetricsIntegration:
|
||||
"""Tests d'intégration des métriques."""
|
||||
|
||||
def test_metrics_with_state_references(self):
|
||||
"""Test: métriques avec références d'état."""
|
||||
S_0 = create_random_tensor(state_id=0, seed=42)
|
||||
x_ref = create_random_tensor(state_id=-1, seed=43)
|
||||
|
||||
metrics = create_metrics(S_0=S_0, x_ref=x_ref)
|
||||
|
||||
# Enregistrer des cycles
|
||||
for _ in range(10):
|
||||
metrics.record_cycle(TriggerType.USER, 0.01)
|
||||
|
||||
report = metrics.compute_daily_report()
|
||||
assert report.cycles.total == 10
|
||||
|
||||
def test_metrics_state_evolution(self):
|
||||
"""Test: métriques d'évolution de l'état."""
|
||||
S_0 = create_random_tensor(state_id=0, seed=42)
|
||||
x_ref = create_random_tensor(state_id=-1, seed=43)
|
||||
X_current = create_random_tensor(state_id=100, seed=44)
|
||||
|
||||
metrics = create_metrics(S_0=S_0, x_ref=x_ref)
|
||||
report = metrics.compute_daily_report(current_state=X_current)
|
||||
|
||||
# Drift calculé
|
||||
assert report.state_evolution.total_drift_from_s0 > 0
|
||||
assert report.state_evolution.drift_from_ref > 0
|
||||
|
||||
def test_metrics_health_status(self):
|
||||
"""Test: statut de santé."""
|
||||
metrics = create_metrics()
|
||||
|
||||
# Sans alertes = healthy
|
||||
status = metrics.get_health_status()
|
||||
assert status['status'] == 'healthy'
|
||||
|
||||
# Avec alerte critical
|
||||
metrics.record_alert("critical", 0.03)
|
||||
status = metrics.get_health_status()
|
||||
assert status['status'] == 'critical'
|
||||
|
||||
|
||||
class TestAmendmentsCompliance:
|
||||
"""Tests de conformité aux amendements."""
|
||||
|
||||
def test_amendment_4_reasoning_markers(self):
|
||||
"""Amendment #4: Marqueurs de raisonnement définis."""
|
||||
assert len(REASONING_MARKERS) > 0
|
||||
|
||||
def test_amendment_5_rumination_probability(self):
|
||||
"""Amendment #5: Probabilité 50% impacts non résolus."""
|
||||
config = DaemonConfig()
|
||||
assert config.prob_unresolved_impact == 0.5
|
||||
|
||||
def test_amendment_6_memory_efficient(self):
|
||||
"""Amendment #6: Tenseur efficace en mémoire."""
|
||||
tensor = create_random_tensor(state_id=0, seed=42)
|
||||
flat = tensor.to_flat()
|
||||
|
||||
# 8 × 1024 = 8192 floats
|
||||
assert flat.shape == (8 * EMBEDDING_DIM,)
|
||||
|
||||
# < 64 KB
|
||||
assert flat.nbytes <= 64 * 1024
|
||||
|
||||
def test_amendment_15_xref_not_attractor(self):
|
||||
"""Amendment #15: x_ref est garde-fou, pas attracteur."""
|
||||
x_ref = create_random_tensor(state_id=-1, seed=42)
|
||||
vigilance = VigilanceSystem(x_ref=x_ref)
|
||||
|
||||
# x_ref a state_id = -1
|
||||
assert vigilance.x_ref.state_id == -1
|
||||
|
||||
# Vigilance n'attire pas vers x_ref, elle observe
|
||||
X = create_random_tensor(state_id=5, seed=123)
|
||||
X_before = X.to_flat().copy()
|
||||
|
||||
vigilance.check_drift(X)
|
||||
|
||||
# L'état n'a pas été modifié
|
||||
assert np.allclose(X_before, X.to_flat())
|
||||
|
||||
|
||||
class TestEndToEndSimplified:
|
||||
"""Tests end-to-end simplifiés."""
|
||||
|
||||
def test_vigilance_with_metrics(self):
|
||||
"""Test: vigilance intégrée avec métriques."""
|
||||
x_ref = create_random_tensor(state_id=-1, seed=42)
|
||||
vigilance = VigilanceSystem(x_ref=x_ref)
|
||||
metrics = create_metrics(x_ref=x_ref)
|
||||
|
||||
# Simuler évolution
|
||||
for i in range(5):
|
||||
X = create_random_tensor(state_id=i, seed=i + 100)
|
||||
alert = vigilance.check_drift(X)
|
||||
metrics.record_alert(alert.level, vigilance.cumulative_drift)
|
||||
|
||||
# Métriques enregistrées
|
||||
report = metrics.compute_daily_report()
|
||||
assert report.alerts.total == 5
|
||||
|
||||
def test_state_evolution_tracked(self):
|
||||
"""Test: évolution d'état suivie."""
|
||||
S_0 = create_random_tensor(state_id=0, seed=42)
|
||||
x_ref = create_random_tensor(state_id=-1, seed=43)
|
||||
|
||||
vigilance = VigilanceSystem(x_ref=x_ref)
|
||||
metrics = create_metrics(S_0=S_0, x_ref=x_ref)
|
||||
|
||||
# Simuler 10 cycles
|
||||
current_state = S_0
|
||||
for i in range(10):
|
||||
# Enregistrer cycle
|
||||
metrics.record_cycle(TriggerType.USER, 0.01)
|
||||
|
||||
# Créer nouvel état (simulation)
|
||||
current_state = create_random_tensor(state_id=i + 1, seed=i + 50)
|
||||
|
||||
# Vérifier vigilance
|
||||
alert = vigilance.check_drift(current_state)
|
||||
metrics.record_alert(alert.level, vigilance.cumulative_drift)
|
||||
|
||||
# Rapport final
|
||||
report = metrics.compute_daily_report(current_state=current_state)
|
||||
|
||||
assert report.cycles.total == 10
|
||||
assert report.state_evolution.total_drift_from_s0 > 0
|
||||
assert report.state_evolution.drift_from_ref > 0
|
||||
|
||||
def test_full_module_imports(self):
|
||||
"""Test: tous les modules s'importent correctement."""
|
||||
from ikario_processual import (
|
||||
# V1
|
||||
OccasionLog,
|
||||
OccasionLogger,
|
||||
OccasionManager,
|
||||
# V2
|
||||
StateTensor,
|
||||
DissonanceResult,
|
||||
FixationResult,
|
||||
VigilanceSystem,
|
||||
StateToLanguage,
|
||||
IkarioDaemon,
|
||||
ProcessMetrics,
|
||||
)
|
||||
|
||||
# Tous les imports fonctionnent
|
||||
assert StateTensor is not None
|
||||
assert DissonanceResult is not None
|
||||
assert VigilanceSystem is not None
|
||||
assert StateToLanguage is not None
|
||||
assert IkarioDaemon is not None
|
||||
assert ProcessMetrics is not None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
Reference in New Issue
Block a user