# =============================================================================
# Mode Simulation (fallback si SK non disponible)
# =============================================================================
# Flag global pour le mode démo (réponses hardcodées pédagogiques)
USE_DEMO_MODE = os.getenv("USE_DEMO_MODE", "false").lower() == "true"
class SimpleAgent:
"""
Agent simplifie pour simulation ou fallback.
Modes disponibles:
- DEMO mode (USE_DEMO_MODE=True): Réponses hardcodées pour les 4 démos pédagogiques
- Simulation mode (use_simulation=True): Logique générique basée sur les théorèmes
- LLM mode (use_simulation=False): Appels réels à OpenAI avec function calling
"""
def __init__(
self,
name: str,
instructions: str,
plugins: Dict[str, Any],
use_simulation: bool = True
):
self.name = name
self.instructions = instructions
self.plugins = plugins
self.use_simulation = use_simulation
self._openai_client = None
# Initialiser le client OpenAI si mode réel
if not use_simulation:
try:
from openai import OpenAI
api_key = os.getenv("OPENAI_API_KEY")
if api_key and len(api_key) > 10 and not api_key.startswith("sk-..."):
self._openai_client = OpenAI(api_key=api_key)
except ImportError:
pass
def _build_openai_tools(self) -> list:
"""Construit les outils au format OpenAI function calling."""
import inspect
tools = []
for plugin_name, plugin in self.plugins.items():
for attr_name in dir(plugin):
attr = getattr(plugin, attr_name)
if not callable(attr):
continue
# Supporter les deux décorateurs
is_sk_func = hasattr(attr, '_sk_function') or hasattr(attr, '__kernel_function__')
if not is_sk_func:
continue
sig = inspect.signature(attr)
properties = {}
required = []
for param_name, param in sig.parameters.items():
if param_name == 'self':
continue
param_type = "string"
if param.annotation != inspect.Parameter.empty:
if param.annotation == bool:
param_type = "boolean"
elif param.annotation in (int, float):
param_type = "number"
properties[param_name] = {
"type": param_type,
"description": f"Parameter {param_name}"
}
if param.default == inspect.Parameter.empty:
required.append(param_name)
# Obtenir nom et description
if hasattr(attr, '__kernel_function_name__'):
func_name = attr.__kernel_function_name__
func_desc = getattr(attr, "__kernel_function_description__", "")
elif hasattr(attr, '_sk_name'):
func_name = attr._sk_name
func_desc = getattr(attr, "_sk_description", "")
else:
func_name = attr_name
func_desc = ""
tools.append({
"type": "function",
"function": {
"name": f"{plugin_name}__{func_name}",
"description": func_desc,
"parameters": {
"type": "object",
"properties": properties,
"required": required
}
}
})
return tools
def _execute_tool_call(self, tool_name: str, arguments: dict) -> str:
"""Exécute un appel de fonction sur un plugin."""
parts = tool_name.split("__", 1)
if len(parts) != 2:
return f"Erreur: format invalide: {tool_name}"
plugin_name, func_name = parts
plugin = self.plugins.get(plugin_name)
if not plugin:
return f"Erreur: plugin {plugin_name} non trouve"
for attr_name in dir(plugin):
attr = getattr(plugin, attr_name)
if not callable(attr):
continue
is_sk = hasattr(attr, '_sk_function') or hasattr(attr, '__kernel_function__')
if not is_sk:
continue
if hasattr(attr, '__kernel_function_name__'):
name = attr.__kernel_function_name__
elif hasattr(attr, '_sk_name'):
name = attr._sk_name
else:
name = attr_name
if name == func_name:
try:
result = attr(**arguments)
return str(result)
except Exception as e:
return f"Erreur {func_name}: {e}"
return f"Erreur: {func_name} non trouve dans {plugin_name}"
def invoke(self, message: str, state: ProofState) -> str:
"""Exécute l'agent sur un message."""
state.increment_iteration()
if self.use_simulation or not self._openai_client:
return self._simulate_response(message, state)
else:
return self._call_llm(message, state)
def _simulate_response(self, message: str, state: ProofState) -> str:
"""
Simulation realiste basee sur l'analyse du théorème.
Si USE_DEMO_MODE=True, utilise les réponses hardcodées pour les DEMOs.
Sinon, utilise la logique générique.
"""
theorem = state.theorem_statement.lower()
goal = state.current_goal or ""
if self.name == "SearchAgent":
return self._do_search(state, theorem, goal)
elif self.name == "TacticAgent":
return self._do_tactic(state, theorem, goal)
elif self.name == "VerifierAgent":
return self._do_verify(state, theorem, goal)
elif self.name == "CriticAgent":
return self._do_critic(state, theorem)
elif self.name == "CoordinatorAgent":
return self._do_coordinate(state, theorem)
return f"[{self.name}] Action simulee."
# =========================================================================
# SEARCH AGENT
# =========================================================================
def _do_search(self, state: ProofState, theorem: str, goal: str) -> str:
"""Recherche de lemmes - Mode DEMO ou générique."""
state_mgr = self.plugins.get("state")
search = self.plugins.get("search")
if not state_mgr:
return "[SearchAgent] Plugins manquants."
# --- MODE DEMO: Réponses hardcodées pour les 4 démos pédagogiques ---
if USE_DEMO_MODE:
# DEMO_1: n = n (reflexivité)
if "n = n" in theorem and "demo_rfl" in theorem:
state_mgr.add_lemma("Eq.refl")
state_mgr.designate_next_agent("TacticAgent")
return "[SearchAgent] Lemme trouve: Eq.refl (reflexivite). -> TacticAgent"
# DEMO_2: 0 + n = n (zero_add)
if "0 + n = n" in theorem or "zero_add" in theorem:
state_mgr.add_lemma("Nat.zero_add")
state_mgr.add_lemma("Nat.add_zero")
state_mgr.designate_next_agent("TacticAgent")
return "[SearchAgent] Lemmes: Nat.zero_add, Nat.add_zero. -> TacticAgent"
# DEMO_3: a * c + b * c = (a + b) * c (distributivité)
if "a * c + b * c" in theorem or "add_mul_distrib" in theorem:
state_mgr.add_lemma("Nat.add_mul")
state_mgr.add_lemma("Nat.mul_add")
state_mgr.add_lemma("Nat.right_distrib")
state_mgr.designate_next_agent("TacticAgent")
return "[SearchAgent] Lemmes distributivite: Nat.add_mul, Nat.mul_add, Nat.right_distrib. -> TacticAgent"
# DEMO_4: m * n = n * m (commutativité multiplication)
if "m * n = n * m" in theorem or "mul_comm_manual" in theorem:
state_mgr.add_lemma("Nat.mul_comm")
state_mgr.add_lemma("Nat.mul_succ")
state_mgr.add_lemma("Nat.succ_mul")
state_mgr.add_lemma("Nat.mul_zero")
state_mgr.add_lemma("Nat.zero_mul")
state_mgr.designate_next_agent("TacticAgent")
return "[SearchAgent] Lemmes pour induction: mul_succ, succ_mul, mul_zero, zero_mul. -> TacticAgent"
# --- MODE GÉNÉRIQUE: Logique basée sur l'analyse du théorème ---
lemmas_found = []
# Réflexivité
if "n = n" in theorem or goal.strip() == "n = n":
if hasattr(state_mgr, 'add_discovered_lemma'):
state_mgr.add_discovered_lemma("Eq.refl", "a = a", "Logic", 1.0)
else:
state_mgr.add_lemma("Eq.refl")
lemmas_found.append("Eq.refl")
# Addition avec zéro
if "+ 0" in theorem or "0 +" in theorem:
if hasattr(state_mgr, 'add_discovered_lemma'):
state_mgr.add_discovered_lemma("Nat.add_zero", "n + 0 = n", "Nat", 0.9)
state_mgr.add_discovered_lemma("Nat.zero_add", "0 + n = n", "Nat", 0.9)
else:
state_mgr.add_lemma("Nat.add_zero")
state_mgr.add_lemma("Nat.zero_add")
lemmas_found.extend(["Nat.add_zero", "Nat.zero_add"])
# Commutativité addition
if "+" in theorem and ("m + n" in theorem or "n + m" in theorem or "b + a" in theorem):
if hasattr(state_mgr, 'add_discovered_lemma'):
state_mgr.add_discovered_lemma("Nat.add_comm", "n + m = m + n", "Nat", 0.85)
else:
state_mgr.add_lemma("Nat.add_comm")
lemmas_found.append("Nat.add_comm")
# Associativité
if theorem.count("+") >= 2:
if hasattr(state_mgr, 'add_discovered_lemma'):
state_mgr.add_discovered_lemma("Nat.add_assoc", "(n + m) + k = n + (m + k)", "Nat", 0.8)
else:
state_mgr.add_lemma("Nat.add_assoc")
lemmas_found.append("Nat.add_assoc")
# Distributivité
if "*" in theorem and "+" in theorem:
if hasattr(state_mgr, 'add_discovered_lemma'):
state_mgr.add_discovered_lemma("Nat.right_distrib", "(n + m) * k = n * k + m * k", "Nat", 0.9)
state_mgr.add_discovered_lemma("Nat.add_mul", "a * c + b * c = (a + b) * c", "Nat", 0.9)
else:
state_mgr.add_lemma("Nat.right_distrib")
state_mgr.add_lemma("Nat.add_mul")
lemmas_found.extend(["Nat.right_distrib", "Nat.add_mul"])
# Commutativité multiplication
if "*" in theorem and ("m * n" in theorem or "n * m" in theorem):
if hasattr(state_mgr, 'add_discovered_lemma'):
state_mgr.add_discovered_lemma("Nat.mul_comm", "m * n = n * m", "Nat", 0.85)
else:
state_mgr.add_lemma("Nat.mul_comm")
lemmas_found.append("Nat.mul_comm")
state_mgr.designate_next_agent("TacticAgent")
if lemmas_found:
return f"[SearchAgent] Lemmes: {', '.join(lemmas_found[:3])}. -> TacticAgent"
return "[SearchAgent] Recherche generique. -> TacticAgent"
# =========================================================================
# TACTIC AGENT
# =========================================================================
def _do_tactic(self, state: ProofState, theorem: str, goal: str) -> str:
"""Generation de tactiques - Mode DEMO ou générique."""
state_mgr = self.plugins.get("state")
if not state_mgr:
return "[TacticAgent] Plugin manquant."
n = len(state.tactics_history)
# --- MODE DEMO: Séquences hardcodées pour progression pédagogique ---
if USE_DEMO_MODE:
# DEMO_1: n = n (reflexivity) - SUCCESS IMMEDIAT
if "n = n" in theorem and "demo_rfl" in theorem:
state_mgr.log_tactic_attempt("rfl", goal, 1.0, "Reflexivite directe")
state_mgr.designate_next_agent("VerifierAgent")
return "[TacticAgent] Tactique: rfl (reflexivite). -> VerifierAgent"
# DEMO_2: 0 + n = n - 2 ECHECS AVANT SUCCES
if "0 + n = n" in theorem or "zero_add" in theorem:
if n == 0:
state_mgr.log_tactic_attempt("rfl", goal, 0.3, "Tentative naive")
state_mgr.designate_next_agent("VerifierAgent")
return "[TacticAgent] Tentative 1: rfl (devrait echouer). -> VerifierAgent"
elif n == 1:
state_mgr.log_tactic_attempt("simp", goal, 0.4, "Simplification")
state_mgr.designate_next_agent("VerifierAgent")
return "[TacticAgent] Tentative 2: simp (insuffisant). -> VerifierAgent"
else:
state_mgr.log_tactic_attempt("exact Nat.zero_add n", goal, 0.95, "Lemme exact")
state_mgr.designate_next_agent("VerifierAgent")
return "[TacticAgent] Tactique: exact Nat.zero_add n. -> VerifierAgent"
# DEMO_3: a * c + b * c = (a + b) * c - 4 ECHECS AVANT SUCCES
if "a * c + b * c" in theorem or "add_mul_distrib" in theorem:
tactics_sequence = [
("rfl", 0.2, "Tentative naive"),
("simp", 0.3, "Simplification basique"),
("ring", 0.4, "Solveur arithmetique"),
("rw [Nat.add_mul]", 0.5, "Réécriture partielle"),
("rw [<- Nat.add_mul]", 0.95, "Forme correcte de distributivité")
]
if n < len(tactics_sequence):
tactic, conf, desc = tactics_sequence[n]
state_mgr.log_tactic_attempt(tactic, goal, conf, desc)
state_mgr.designate_next_agent("VerifierAgent")
return f"[TacticAgent] Tentative {n+1}: {tactic}. -> VerifierAgent"
else:
state_mgr.log_tactic_attempt("rw [<- Nat.add_mul]", goal, 0.95, "Solution")
state_mgr.designate_next_agent("VerifierAgent")
return "[TacticAgent] Tactique finale: rw [<- Nat.add_mul]. -> VerifierAgent"
# DEMO_4: m * n = n * m - Exploration par induction (8-10 iterations)
if "m * n = n * m" in theorem or "mul_comm_manual" in theorem:
tactics_sequence = [
("rfl", 0.1, "Tentative naive"),
("simp", 0.15, "Simplification"),
("ring", 0.2, "Ring ne suffit pas pour axiomes"),
("omega", 0.2, "Omega: arithmétique linéaire seulement"),
("induction m", 0.4, "Induction sur m"),
("exact Nat.mul_zero n", 0.5, "Cas de base"),
("simp [Nat.succ_mul]", 0.6, "Cas inductif étape 1"),
("rw [ih]", 0.7, "Utiliser hypothèse induction"),
("simp [Nat.mul_succ]", 0.8, "Transformation finale"),
("exact Nat.mul_comm m n", 0.95, "Lemme direct")
]
if n < len(tactics_sequence):
tactic, conf, desc = tactics_sequence[n]
state_mgr.log_tactic_attempt(tactic, goal, conf, desc)
state_mgr.designate_next_agent("VerifierAgent")
return f"[TacticAgent] Tentative {n+1}: {tactic}. -> VerifierAgent"
else:
state_mgr.log_tactic_attempt("exact Nat.mul_comm m n", goal, 0.95, "Solution")
state_mgr.designate_next_agent("VerifierAgent")
return "[TacticAgent] Tactique finale: exact Nat.mul_comm. -> VerifierAgent"
# --- MODE GÉNÉRIQUE: Stratégie adaptative ---
# Stratégie: essayer les tactiques simples d'abord
if n == 0:
tactic = "rfl"
desc = "Reflexivite"
elif n == 1:
tactic = "simp"
desc = "Simplification"
elif n == 2:
# Utiliser les lemmes trouvés
if state.lemmas_found:
lemma = state.lemmas_found[0].name if hasattr(state.lemmas_found[0], 'name') else str(state.lemmas_found[0])
tactic = f"exact {lemma}"
desc = "Lemme exact"
else:
tactic = "ring"
desc = "Ring solver"
elif n == 3:
tactic = "omega"
desc = "Arithmétique linéaire"
elif n == 4:
tactic = "linarith"
desc = "Arithmétique linéaire avancée"
else:
tactic = "sorry"
desc = "Abandon (preuve incomplète)"
state_mgr.log_tactic_attempt(tactic, goal, 0.3 + n * 0.1, desc)
state_mgr.designate_next_agent("VerifierAgent")
return f"[TacticAgent] Tactique: {tactic}. -> VerifierAgent"
# =========================================================================
# VERIFIER AGENT
# =========================================================================
def _do_verify(self, state: ProofState, theorem: str, goal: str) -> str:
"""Vérification de la preuve."""
state_mgr = self.plugins.get("state")
if not state_mgr or not state.tactics_history:
return "[VerifierAgent] Rien a verifier."
last = state.tactics_history[-1]
n = len(state.tactics_history)
attempt_id = f"attempt_{n}"
# --- MODE DEMO: Vérification hardcodée ---
if USE_DEMO_MODE:
# DEMO_1: n = n - rfl réussit immédiatement
if "rfl" in last.tactic and "n = n" in theorem and "demo_rfl" in theorem:
state_mgr.add_verification_result(attempt_id, True, "OK", "", "", 50.0)
state_mgr.set_proof_complete(last.tactic)
return f"[VerifierAgent] SUCCES! Preuve par reflexivite: {last.tactic}"
# DEMO_2: 0 + n = n - succès à la 3ème tentative
if "0 + n = n" in theorem or "zero_add" in theorem:
if n < 3:
state_mgr.add_verification_result(attempt_id, False, f"Tentative {n}", "echec", "", 80.0)
state_mgr.designate_next_agent("CriticAgent")
return f"[VerifierAgent] Echec tentative {n}. -> CriticAgent"
else:
state_mgr.add_verification_result(attempt_id, True, "OK", "", "", 100.0)
state_mgr.set_proof_complete(last.tactic)
return f"[VerifierAgent] SUCCES apres {n} tentatives! {last.tactic}"
# DEMO_3: distributivité - succès à la 5ème tentative
if "a * c + b * c" in theorem or "add_mul_distrib" in theorem:
if n < 5:
state_mgr.add_verification_result(attempt_id, False, f"{n}/5", "continue", "", 100.0)
state_mgr.designate_next_agent("CriticAgent")
return f"[VerifierAgent] Etape {n}/5. -> CriticAgent"
else:
state_mgr.add_verification_result(attempt_id, True, "OK", "", "", 150.0)
state_mgr.set_proof_complete(last.tactic)
return f"[VerifierAgent] SUCCES! Distributivite prouvee apres {n} etapes."
# DEMO_4: mul_comm - succès à la 10ème tentative
if "m * n = n * m" in theorem or "mul_comm_manual" in theorem:
if n < 10:
state_mgr.add_verification_result(attempt_id, False, f"{n}/10", "continue", "", 120.0)
state_mgr.designate_next_agent("CriticAgent")
return f"[VerifierAgent] Etape {n}/10. -> CriticAgent"
else:
state_mgr.add_verification_result(attempt_id, True, "OK", "", "", 200.0)
state_mgr.set_proof_complete(last.tactic)
return f"[VerifierAgent] SUCCES! Commutativite prouvee apres {n} iterations."
# --- MODE GÉNÉRIQUE: Vérification simulée ---
# Simuler la vérification (en mode réel, appeler Lean)
success_tactics = ["rfl", "simp", "ring", "omega", "linarith", "exact"]
is_success = any(t in last.tactic for t in success_tactics) and n >= 2
if is_success or "sorry" in last.tactic:
state_mgr.add_verification_result(attempt_id, True, "OK", "", "", 100.0)
state_mgr.set_proof_complete(last.tactic)
return f"[VerifierAgent] SUCCES! {last.tactic}"
else:
state_mgr.add_verification_result(attempt_id, False, goal, "echec", "", 50.0)
state_mgr.designate_next_agent("CriticAgent")
return f"[VerifierAgent] Echec: {last.tactic}. -> CriticAgent"
# =========================================================================
# CRITIC AGENT
# =========================================================================
def _do_critic(self, state: ProofState, theorem: str) -> str:
"""Analyse critique et feedback."""
state_mgr = self.plugins.get("state")
if not state_mgr:
return "[CriticAgent] Plugin manquant."
n = len(state.tactics_history)
# --- MODE DEMO: Feedback pédagogique ---
if USE_DEMO_MODE:
if "0 + n = n" in theorem or "zero_add" in theorem:
if n == 1:
state_mgr.designate_next_agent("TacticAgent")
return "[CriticAgent] rfl echoue car 0+n n'est pas syntaxiquement n. Essayer simp. -> TacticAgent"
else:
state_mgr.designate_next_agent("TacticAgent")
return "[CriticAgent] simp ne suffit pas. Utiliser le lemme Nat.zero_add directement. -> TacticAgent"
if "a * c + b * c" in theorem or "add_mul_distrib" in theorem:
hints = [
"rfl echoue: pas d'égalité syntaxique.",
"simp ne connait pas cette forme de distributivité.",
"ring ne peut pas gérer les Nat directement.",
"Essayer rw avec Nat.add_mul dans le bon sens."
]
hint = hints[min(n-1, len(hints)-1)]
state_mgr.designate_next_agent("TacticAgent")
return f"[CriticAgent] {hint} -> TacticAgent"
if "m * n = n * m" in theorem or "mul_comm_manual" in theorem:
if n < 5:
state_mgr.designate_next_agent("TacticAgent")
return f"[CriticAgent] Tentative {n} echouee. Explorer l'induction. -> TacticAgent"
else:
state_mgr.designate_next_agent("CoordinatorAgent")
return "[CriticAgent] Besoin de coordination pour stratégie induction. -> CoordinatorAgent"
# --- MODE GÉNÉRIQUE ---
state_mgr.designate_next_agent("TacticAgent")
return f"[CriticAgent] Tentative {n} echouee. Essayer une autre approche. -> TacticAgent"
# =========================================================================
# COORDINATOR AGENT
# =========================================================================
def _do_coordinate(self, state: ProofState, theorem: str) -> str:
"""Coordination de la stratégie globale."""
state_mgr = self.plugins.get("state")
if not state_mgr:
return "[CoordinatorAgent] Plugin manquant."
# --- MODE DEMO ---
if USE_DEMO_MODE:
if "a * c + b * c" in theorem or "add_mul_distrib" in theorem:
state_mgr.set_proof_strategy("distributivity_rewrite")
state_mgr.designate_next_agent("TacticAgent")
return "[CoordinatorAgent] Strategie: réécriture avec Nat.add_mul. -> TacticAgent"
if "m * n = n * m" in theorem or "mul_comm_manual" in theorem:
state_mgr.set_proof_strategy("induction_then_lemma")
state_mgr.designate_next_agent("TacticAgent")
return "[CoordinatorAgent] Strategie: induction puis lemme direct. -> TacticAgent"
# --- MODE GÉNÉRIQUE ---
if "*" in theorem and "+" in theorem:
state_mgr.set_proof_strategy("ring_solver")
state_mgr.designate_next_agent("TacticAgent")
return "[CoordinatorAgent] Strategie: ring solver pour arithmétique. -> TacticAgent"
if theorem.count("+") >= 2 or theorem.count("*") >= 2:
state_mgr.set_proof_strategy("ac_normalization")
state_mgr.designate_next_agent("TacticAgent")
return "[CoordinatorAgent] Strategie: AC normalization. -> TacticAgent"
state_mgr.designate_next_agent("TacticAgent")
return "[CoordinatorAgent] Strategie par defaut. -> TacticAgent"
# =========================================================================
# LLM MODE (Appels réels à OpenAI)
# =========================================================================
def _call_llm(self, message: str, state: ProofState) -> str:
"""Appelle le LLM OpenAI avec function calling."""
state_summary = json.dumps(state.get_state_snapshot(summarize=True), indent=2)
tools = self._build_openai_tools()
nl = chr(10)
user_content = f"ETAT ACTUEL:{nl}{state_summary}{nl}{nl}TACHE:{nl}{message}"
messages = [
{"role": "system", "content": self.instructions},
{"role": "user", "content": user_content}
]
max_tool_calls = 10
tool_results = []
for iteration in range(max_tool_calls):
try:
model = os.getenv("OPENAI_CHAT_MODEL_ID", "gpt-5.6-sol")
use_mct = any(model.startswith(p) for p in ('gpt-4.5', 'gpt-5', 'o1', 'o3'))
token_param = {"max_completion_tokens": 1000} if use_mct else {"max_tokens": 1000}
response = self._openai_client.chat.completions.create(
model=model,
messages=messages,
tools=tools if tools else None,
tool_choice="auto" if tools else None,
temperature=0.3,
**token_param
)
assistant_message = response.choices[0].message
if assistant_message.tool_calls:
messages.append(assistant_message.model_dump())
for tool_call in assistant_message.tool_calls:
func_name = tool_call.function.name
try:
arguments = json.loads(tool_call.function.arguments)
except json.JSONDecodeError:
arguments = {}
result = self._execute_tool_call(func_name, arguments)
tool_results.append(func_name.split("__")[-1])
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
else:
final_response = assistant_message.content or "(pas de reponse)"
if tool_results:
actions = ", ".join(tool_results[:5])
final_response = f"Actions: {actions}{nl}{final_response}"
return f"[{self.name}] {final_response}"
except Exception as e:
return f"[{self.name}] Erreur LLM: {e}"
actions = ", ".join(tool_results[:5])
return f"[{self.name}] Max tool calls. Actions: {actions}"
print("SimpleAgent: classe initialisee")