Files
linear-coding-agent/ikario_processual/state_transformation.py
David Blanc Brioir 6af52866ed Add Phases 3-5: State transformation, OccasionLogger, OccasionManager
Phase 3 - State Transformation:
- transform_state() function with alpha/beta parameters
- compute_adaptive_params() for dynamic transformation
- StateTransformer class for state management

Phase 4 - Occasion Logger:
- OccasionLog dataclass for structured logging
- OccasionLogger for JSON file storage
- Profile evolution tracking and statistics

Phase 5 - Occasion Manager:
- Full cycle: Prehension → Concrescence → Satisfaction
- Search integration (thoughts, library)
- State creation and logging orchestration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 17:09:36 +01:00

259 lines
7.9 KiB
Python

#!/usr/bin/env python3
"""
StateTransformation - Fonction de transition d'état S(t-1) → S(t).
Implémente la transformation processuelle selon Whitehead:
- Préhension: récupérer le contexte
- Concrescence: intégrer l'occasion
- Satisfaction: nouvel état stable
La formule de base:
S(t) = normalize(alpha * S(t-1) + beta * occasion_embedding)
où alpha (inertie) + beta (nouveauté) = 1
"""
import os
from datetime import datetime
from typing import Tuple, Dict, Any, Optional
import numpy as np
import requests
WEAVIATE_URL = os.getenv("WEAVIATE_URL", "http://localhost:8080")
def transform_state(
s_prev: np.ndarray,
occasion_embedding: np.ndarray,
alpha: float = 0.85,
beta: float = 0.15
) -> np.ndarray:
"""
Transforme S(t-1) en S(t) via l'occasion.
Le résultat est renormalisé pour rester sur l'hypersphère unitaire.
Args:
s_prev: Vecteur d'état précédent (normalisé)
occasion_embedding: Embedding de l'occasion/réponse (normalisé)
alpha: Coefficient d'inertie (conservation de l'identité)
beta: Coefficient de nouveauté (intégration de l'occasion)
Returns:
Nouveau vecteur d'état normalisé
"""
# Combinaison pondérée
s_new = alpha * s_prev + beta * occasion_embedding
# Renormalisation sur l'hypersphère
norm = np.linalg.norm(s_new)
if norm > 0:
s_new = s_new / norm
return s_new
def compute_adaptive_params(
occasion: Dict[str, Any],
base_alpha: float = 0.85,
base_beta: float = 0.15
) -> Tuple[float, float]:
"""
Calcule alpha/beta adaptatifs selon l'intensité de l'occasion.
Heuristiques:
- Plus de pensées créées → plus de beta (plus de changement)
- Trigger "timer" → moins de beta (auto-réflexion douce)
- Trigger "user" significatif → plus de beta
Args:
occasion: Dictionnaire avec trigger_type, thoughts_created, etc.
base_alpha: Alpha de base
base_beta: Beta de base
Returns:
Tuple (alpha, beta) ajustés
"""
# Ajuster selon le nombre de pensées créées
thoughts_count = occasion.get('thoughts_created', 0)
intensity = min(thoughts_count * 0.03, 0.10) # Max +10% de beta
# Trigger timer = plus d'inertie (réflexion douce)
if occasion.get('trigger_type') == 'timer':
intensity = intensity * 0.5
# Trigger user avec contenu long = plus d'impact
trigger_content = occasion.get('trigger_content', '')
if occasion.get('trigger_type') == 'user' and len(trigger_content) > 200:
intensity = intensity + 0.02
alpha = base_alpha - intensity
beta = base_beta + intensity
# S'assurer que alpha + beta = 1
total = alpha + beta
alpha = alpha / total
beta = beta / total
return alpha, beta
class StateTransformer:
"""Gère les transformations d'état et la persistance."""
def __init__(self, embedding_model=None):
"""
Args:
embedding_model: Modèle SentenceTransformer (chargé à la demande si None)
"""
self._model = embedding_model
self._model_loaded = embedding_model is not None
@property
def model(self):
"""Charge le modèle d'embedding à la demande."""
if not self._model_loaded:
from sentence_transformers import SentenceTransformer
print("[StateTransformer] Chargement du modèle BGE-M3...")
self._model = SentenceTransformer('BAAI/bge-m3')
self._model_loaded = True
return self._model
def get_current_state_id(self) -> int:
"""Retourne l'ID de l'état le plus récent."""
url = f"{WEAVIATE_URL}/v1/objects?class=StateVector&limit=100"
response = requests.get(url)
if response.status_code != 200:
return -1
objects = response.json().get("objects", [])
if not objects:
return -1
return max(obj.get("properties", {}).get("state_id", -1) for obj in objects)
def get_state_vector(self, state_id: int) -> Optional[np.ndarray]:
"""Récupère le vecteur d'un état."""
query = {
"query": """
{
Get {
StateVector(where: {
path: ["state_id"],
operator: Equal,
valueInt: %d
}) {
_additional {
vector
}
}
}
}
""" % state_id
}
response = requests.post(
f"{WEAVIATE_URL}/v1/graphql",
json=query,
headers={"Content-Type": "application/json"}
)
if response.status_code != 200:
return None
data = response.json()
states = data.get("data", {}).get("Get", {}).get("StateVector", [])
if not states:
return None
vector = states[0].get("_additional", {}).get("vector")
return np.array(vector) if vector else None
def create_new_state(
self,
occasion: Dict[str, Any],
response_text: str,
thoughts_created: int = 0
) -> int:
"""
Crée un nouvel état à partir de l'occasion.
Args:
occasion: {trigger_type, trigger_content, summary}
response_text: Texte de la réponse générée
thoughts_created: Nombre de pensées créées
Returns:
new_state_id
"""
# 1. Récupérer S(t-1)
current_id = self.get_current_state_id()
s_prev = self.get_state_vector(current_id)
if s_prev is None:
raise ValueError(f"État S({current_id}) non trouvé")
# 2. Calculer l'embedding de la réponse
occasion_embedding = self.model.encode(response_text)
occasion_embedding = occasion_embedding / np.linalg.norm(occasion_embedding)
# 3. Calculer les paramètres adaptatifs
alpha, beta = compute_adaptive_params({
'thoughts_created': thoughts_created,
'trigger_type': occasion.get('trigger_type', 'user'),
'trigger_content': occasion.get('trigger_content', '')
})
# 4. Transformer
s_new = transform_state(s_prev, occasion_embedding, alpha, beta)
# 5. Persister
new_state_id = current_id + 1
state_obj = {
"state_id": new_state_id,
"timestamp": datetime.now().isoformat() + "Z",
"previous_state_id": current_id,
"trigger_type": occasion.get('trigger_type', 'user'),
"trigger_content": occasion.get('trigger_content', '')[:500],
"occasion_summary": occasion.get('summary', '')[:500],
"response_summary": response_text[:500],
"thoughts_created": thoughts_created,
"source_thoughts_count": 0,
"source_messages_count": 0,
}
response = requests.post(
f"{WEAVIATE_URL}/v1/objects",
json={
"class": "StateVector",
"properties": state_obj,
"vector": s_new.tolist()
},
headers={"Content-Type": "application/json"}
)
if response.status_code not in [200, 201]:
raise RuntimeError(f"Erreur création S({new_state_id}): {response.text}")
print(f"[StateTransformer] État S({new_state_id}) créé (alpha={alpha:.2f}, beta={beta:.2f})")
return new_state_id
# Test simple
if __name__ == "__main__":
# Test de la transformation
s_prev = np.random.randn(1024)
s_prev = s_prev / np.linalg.norm(s_prev)
occasion = np.random.randn(1024)
occasion = occasion / np.linalg.norm(occasion)
s_new = transform_state(s_prev, occasion, alpha=0.85, beta=0.15)
print(f"Norme s_prev: {np.linalg.norm(s_prev):.4f}")
print(f"Norme s_new: {np.linalg.norm(s_new):.4f}")
print(f"Similarité s_prev/s_new: {np.dot(s_prev, s_new):.4f}")