|
|
|
|
|
""" |
|
|
engine.py |
|
|
Orquestador principal del motor Savant Simbiótico RRF. |
|
|
Expone: |
|
|
- handle_query(text): detecta intención (map/resonance/music/chat) y responde |
|
|
- access to SimpleTrainer, SelfImprover, MemoryStore for external control |
|
|
""" |
|
|
import time |
|
|
from .mappings import IcosaMap, DodecaMap |
|
|
from .resonance import ResonanceSimulator |
|
|
from .music import MusicAdapter |
|
|
from .memory import MemoryStore |
|
|
from .self_improvement import SelfImprover |
|
|
|
|
|
from .api_helpers import chat_refine |
|
|
import os |
|
|
import pandas as pd |
|
|
import json |
|
|
import pickle |
|
|
|
|
|
class SavantEngine: |
|
|
def __init__(self, structured_data_paths=None): |
|
|
self.memory = MemoryStore("SAVANT_memory.jsonl") |
|
|
|
|
|
self.structured_data = {} |
|
|
if structured_data_paths: |
|
|
print("Engine: Loading structured data...") |
|
|
try: |
|
|
self.structured_data['equations'] = self._load_json_data(structured_data_paths.get('equations')) |
|
|
nodes_raw = self._load_json_data(structured_data_paths.get('icosahedron_nodes')) |
|
|
self.structured_data['icosahedron_nodes'] = nodes_raw.get('nodes', []) if isinstance(nodes_raw, dict) else [] |
|
|
self.structured_data['frequencies'] = self._load_csv_data(structured_data_paths.get('frequencies')) |
|
|
self.structured_data['constants'] = self._load_csv_data(structured_data_paths.get('constants')) |
|
|
|
|
|
print("Engine loaded structured data: Equations={}, Nodes={}, Frequencies={}, Constants={}".format( |
|
|
len(self.structured_data['equations']) if self.structured_data['equations'] else 0, |
|
|
len(self.structured_data['icosahedron_nodes']), |
|
|
len(self.structured_data['frequencies']), |
|
|
len(self.structured_data['constants']) |
|
|
)) |
|
|
except Exception as e: |
|
|
print(f"Engine: Error loading structured data: {e}") |
|
|
self.structured_data = {} |
|
|
|
|
|
|
|
|
|
|
|
self.icosa = IcosaMap(node_data=self.structured_data.get('icosahedron_nodes')) |
|
|
self.dodeca = DodecaMap() |
|
|
self.resonator = ResonanceSimulator(frequencies_data=self.structured_data.get('frequencies'), constants_data=self.structured_data.get('constants')) |
|
|
self.music = MusicAdapter(frequencies_data=self.structured_data.get('frequencies')) |
|
|
self.self_improver = SelfImprover(self.memory, structured_data=self.structured_data) |
|
|
|
|
|
|
|
|
self._interaction_count = 0 |
|
|
|
|
|
|
|
|
|
|
|
def _load_json_data(self, file_path): |
|
|
"""Loads data from a JSON file.""" |
|
|
if not file_path or not os.path.exists(file_path): |
|
|
|
|
|
return None |
|
|
try: |
|
|
with open(file_path, "r", encoding="utf-8") as f: |
|
|
data = json.load(f) |
|
|
|
|
|
return data |
|
|
except json.JSONDecodeError as e: |
|
|
print(f"Error decoding JSON from {file_path}: {e}") |
|
|
return None |
|
|
except Exception as e: |
|
|
print(f"An unexpected error occurred while loading JSON data: {e}") |
|
|
return None |
|
|
|
|
|
def _load_csv_data(self, file_path): |
|
|
"""Loads data from a CSV file using pandas.""" |
|
|
if not file_path or not os.path.exists(file_path): |
|
|
|
|
|
return [] |
|
|
try: |
|
|
df = pd.read_csv(file_path) |
|
|
|
|
|
return df.to_dict(orient='records') |
|
|
except Exception as e: |
|
|
print(f"An error occurred while loading CSV data from {file_path}: {e}") |
|
|
return [] |
|
|
|
|
|
|
|
|
def _classify(self, text): |
|
|
t = text.lower() |
|
|
|
|
|
if any(k in t for k in ("equation", "ecuacion", "hamiltoniano", "dirac", "formula", "formulae", "formulas")): |
|
|
return "equation_query" |
|
|
if any(k in t for k in ("node", "nodo", "icosahedron", "dodecahedron", "poly", "vertex", "point", "map")): |
|
|
|
|
|
words = t.split() |
|
|
if len(words) > 1 and words[-1].isdigit() and words[-2] in ("node", "nodo"): |
|
|
return "node_query" |
|
|
return "node_query" |
|
|
if any(k in t for k in ("frecuen", "freq", "music", "nota", "melod", "tono", "pitch", "scale", "musical", "sound", "audio")): |
|
|
return "music_resonance" |
|
|
if any(k in t for k in ("constant", "constante", "valor", "unidad", "define", "what is the value of")): |
|
|
return "constant_query" |
|
|
if any(k in t for k in ("resonance", "resonar", "resonant", "vibration", "oscilla")): |
|
|
return "resonance_only" |
|
|
|
|
|
|
|
|
|
|
|
if any(k in t for k in ("chat", "hola", "qué", "como", "explica", "tell me", "what is", "describe", "info", "information")): |
|
|
return "chat" |
|
|
return "chat" |
|
|
|
|
|
|
|
|
def handle_query(self, text, base_model_output=None): |
|
|
kind = self._classify(text) |
|
|
|
|
|
|
|
|
if kind == "equation_query": |
|
|
relevant_eqs = [] |
|
|
if self.structured_data.get('equations'): |
|
|
|
|
|
query_words = text.lower().split() |
|
|
relevant_eqs = [eq for eq in self.structured_data['equations'] if any(word in eq.get('nombre', '').lower() or word in eq.get('descripcion', '').lower() or any(comp.lower() in word for comp in eq.get('componentes', [])) for word in query_words)] |
|
|
|
|
|
if relevant_eqs: |
|
|
|
|
|
response_parts = ["Based on the RRF Equations data, I found the following relevant equations:"] |
|
|
for eq in relevant_eqs[:3]: |
|
|
response_parts.append(f"- '{eq.get('nombre', 'N/A')}' ({eq.get('tipo', 'Equation')}): {eq.get('ecuacion', 'N/A')} (Components: {', '.join(eq.get('componentes', []))})") |
|
|
if len(relevant_eqs) > 3: |
|
|
response_parts.append("...") |
|
|
response = "\n".join(response_parts) |
|
|
self._log_interaction(text, base_model_output, response, type="equation_query") |
|
|
return {"type": "equation_query", "query": text, "result": relevant_eqs, "response": response} |
|
|
else: |
|
|
response = "I couldn't find any relevant equations in the loaded data for that query." |
|
|
self._log_interaction(text, base_model_output, response, type="equation_query_not_found") |
|
|
return {"type": "equation_query", "query": text, "result": [], "response": response} |
|
|
|
|
|
|
|
|
if kind == "node_query": |
|
|
relevant_nodes = [] |
|
|
if self.structured_data.get('icosahedron_nodes'): |
|
|
query_words = text.lower().split() |
|
|
|
|
|
try: |
|
|
node_id = int(query_words[-1]) if query_words and query_words[-1].isdigit() else None |
|
|
if node_id is not None: |
|
|
relevant_nodes = [node for node in self.structured_data['icosahedron_nodes'] if node.get('id') == node_id] |
|
|
except (ValueError, IndexError): |
|
|
pass |
|
|
|
|
|
|
|
|
if not relevant_nodes: |
|
|
relevant_nodes = [node for node in self.structured_data['icosahedron_nodes'] if any(word in node.get('description', '').lower() or word in node.get('name', '').lower() for word in query_words)] |
|
|
|
|
|
if relevant_nodes: |
|
|
response_parts = ["Based on the Icosahedron Nodes data, I found the following relevant nodes:"] |
|
|
for node in relevant_nodes[:3]: |
|
|
response_parts.append(f"- Node {node.get('id', 'N/A')}: {node.get('description', node.get('name', 'No description'))} (Coords: ({node.get('x', 'N/A')}, {node.get('y', 'N/A')}, {node.get('z', 'N/A')}))") |
|
|
if len(relevant_nodes) > 3: |
|
|
response_parts.append("...") |
|
|
response = "\n".join(response_parts) |
|
|
self._log_interaction(text, base_model_output, response, type="node_query") |
|
|
return {"type": "node_query", "query": text, "result": relevant_nodes, "response": response} |
|
|
else: |
|
|
response = "I couldn't find any relevant nodes in the loaded data for that query." |
|
|
self._log_interaction(text, base_model_output, response, type="node_query_not_found") |
|
|
return {"type": "node_query", "query": text, "result": [], "response": response} |
|
|
|
|
|
if kind == "music_resonance": |
|
|
|
|
|
|
|
|
response_parts = [] |
|
|
if self.structured_data.get('frequencies') and any(k in text.lower() for k in ("frecuen", "freq", "nota", "pitch", "scale", "musical", "sound", "audio")): |
|
|
query_words = text.lower().split() |
|
|
relevant_freqs = [f for f in self.structured_data['frequencies'] if any(word in f.get('note', '').lower() or word in f.get('role', '').lower() for word in query_words)] |
|
|
if relevant_freqs: |
|
|
response_parts.append("Based on the Frequencies data, I found:") |
|
|
for freq in relevant_freqs[:3]: |
|
|
response_parts.append(f"- Note: {freq.get('note', 'N/A')}, Frequency: {freq.get('frequency', 'N/A')} Hz, Role: {freq.get('role', 'N/A')}") |
|
|
if len(relevant_freqs) > 3: response_parts.append("...") |
|
|
|
|
|
if self.structured_data.get('constants') and any(k in text.lower() for k in ("constant", "constante")): |
|
|
query_words = text.lower().split() |
|
|
relevant_constants = [c for c in self.structured_data['constants'] if any(word in c.get('name', '').lower() for word in query_words)] |
|
|
if relevant_constants: |
|
|
response_parts.append("Based on the Constants data, I found:") |
|
|
for const in relevant_constants[:3]: |
|
|
response_parts.append(f"- Constant: {const.get('name', 'N/A')}, Value: {const.get('value', 'N/A')}, Units: {const.get('units', 'N/A')}") |
|
|
if len(relevant_constants) > 3: response_parts.append("...") |
|
|
|
|
|
|
|
|
r = self.resonator.simulate(text) |
|
|
seq = self.music.adapt_text_to_music(text) |
|
|
|
|
|
response_parts.append(f"Resonance simulation summary: Dominant Frequency={r['summary'].get('dom_freq', 0.0):.4f} Hz, Max Power={r['summary'].get('max_power', 0.0):.4f}.") |
|
|
response_parts.append(f"Adapted to music sequence (first 5 notes: pitch, duration): {seq[:5]}...") |
|
|
|
|
|
response = "\n".join(response_parts) if response_parts else "Processing music and resonance query..." |
|
|
self._log_interaction(text, base_model_output, response, type="music_resonance") |
|
|
return {"type":"music_resonance","query":text,"resonance_result":r,"music_result":seq, "response": response} |
|
|
|
|
|
if kind == "resonance_only": |
|
|
|
|
|
response_parts = [] |
|
|
if self.structured_data.get('constants') and any(k in text.lower() for k in ("constant", "constante")): |
|
|
query_words = text.lower().split() |
|
|
relevant_constants = [c for c in self.structured_data['constants'] if any(word in c.get('name', '').lower() for word in query_words)] |
|
|
if relevant_constants: |
|
|
response_parts.append("Based on the Constants data, I found:") |
|
|
for const in relevant_constants[:3]: |
|
|
response_parts.append(f"- Constant: {const.get('name', 'N/A')}, Value: {const.get('value', 'N/A')}, Units: {const.get('units', 'N/A')}") |
|
|
if len(relevant_constants) > 3: response_parts.append("...") |
|
|
|
|
|
r = self.resonator.simulate(text) |
|
|
response_parts.append(f"Resonance simulation summary: Dominant Frequency={r['summary'].get('dom_freq', 0.0):.4f} Hz, Max Power={r['summary'].get('max_power', 0.0):.4f}.") |
|
|
|
|
|
response = "\n".join(response_parts) if response_parts else "Processing resonance query..." |
|
|
self._log_interaction(text, base_model_output, response, type="resonance_only") |
|
|
return {"type":"resonance_only","query":text,"resonance_result":r, "response": response} |
|
|
|
|
|
|
|
|
if kind == "constant_query": |
|
|
relevant_constants = [] |
|
|
if self.structured_data.get('constants'): |
|
|
query_words = text.lower().split() |
|
|
relevant_constants = [c for c in self.structured_data['constants'] if any(word in c.get('name', '').lower() or word in c.get('units', '').lower() for word in query_words)] |
|
|
|
|
|
if relevant_constants: |
|
|
response_parts = ["Based on the RRF Constants data, I found the following relevant constants:"] |
|
|
for const in relevant_constants[:3]: |
|
|
response_parts.append(f"- Name: {const.get('name', 'N/A')}, Value: {const.get('value', 'N/A')}, Units: {const.get('units', 'N/A')}") |
|
|
if len(relevant_constants) > 3: response_parts.append("...") |
|
|
response = "\n".join(response_parts) |
|
|
self._log_interaction(text, base_model_output, response, type="constant_query") |
|
|
return {"type": "constant_query", "query": text, "result": relevant_constants, "response": response} |
|
|
else: |
|
|
response = "I couldn't find any relevant constants in the loaded data for that query." |
|
|
self._log_interaction(text, base_model_output, response, type="constant_query_not_found") |
|
|
return {"type": "constant_query", "query": text, "result": [], "response": response} |
|
|
|
|
|
|
|
|
if kind == "map": |
|
|
|
|
|
node_label = self.icosa.closest_node(text) |
|
|
response = f"Mapping query '{text}' to closest node: {node_label}" |
|
|
|
|
|
if self.structured_data.get('icosahedron_nodes'): |
|
|
|
|
|
|
|
|
|
|
|
mapped_node_data = next((node for node in self.structured_data['icosahedron_nodes'] if node.get('description', '').lower() == node_label.lower() or node.get('name', '').lower() == node_label.lower()), None) |
|
|
if mapped_node_data: |
|
|
response += f" (ID: {mapped_node_data.get('id', 'N/A')}, Coords: ({mapped_node_data.get('x', 'N/A')}, {mapped_node_data.get('y', 'N/A')}, {mapped_node_data.get('z', 'N/A')}))" |
|
|
|
|
|
|
|
|
self._log_interaction(text, base_model_output, response, type="map") |
|
|
return {"type":"map","query":text,"node":node_label, "response": response} |
|
|
|
|
|
|
|
|
if kind == "chat": |
|
|
if base_model_output is None: |
|
|
|
|
|
base = "Echo: " + text |
|
|
else: |
|
|
base = base_model_output |
|
|
|
|
|
refined = chat_refine(text, base, self_improver=self.self_improver) |
|
|
response = refined |
|
|
self._log_interaction(text, base_model_output, refined, type="chat_interaction") |
|
|
|
|
|
return {"type":"chat","query":text,"base":base,"refined":refined, "response": response} |
|
|
|
|
|
|
|
|
response = "I'm not sure how to handle that query based on the available data and functions." |
|
|
self._log_interaction(text, base_model_output, response, type="unhandled_query") |
|
|
return {"type": "unhandled", "query": text, "response": response} |
|
|
|
|
|
|
|
|
def _log_interaction(self, user_input, base_output, final_output, type="interaction"): |
|
|
"""Logs interaction details to memory and triggers self-improvement if needed.""" |
|
|
interaction_record = { |
|
|
"type": type, |
|
|
"user_input": user_input, |
|
|
"base_model_output": base_output, |
|
|
"final_output": final_output, |
|
|
"_ts": time.time() |
|
|
} |
|
|
self.memory.add(interaction_record) |
|
|
|
|
|
|
|
|
self._interaction_count = getattr(self, '_interaction_count', 0) + 1 |
|
|
if self._interaction_count % 10 == 0: |
|
|
print("SAVANT: Triggering self-improvement cycle...") |
|
|
try: |
|
|
proposal = self.self_improver.propose() |
|
|
accepted, metric = self.self_improver.evaluate_and_apply(proposal) |
|
|
print(f"SAVANT: Self-improvement proposal accepted: {accepted}, New metric: {metric}") |
|
|
self.memory.add({ |
|
|
"type": "self_improvement_triggered", |
|
|
"proposal": proposal, |
|
|
"accepted": accepted, |
|
|
"metric": metric, |
|
|
"_ts": time.time() |
|
|
}) |
|
|
except Exception as si_error: |
|
|
|
|
|
error_message = f"Error during self-improvement: {si_error}" |
|
|
print(f"SAVANT: {error_message}") |
|
|
self.memory.add({ |
|
|
"type": "self_improvement_error", |
|
|
"error": error_message, |
|
|
"_ts": time.time() |
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def propose_improvement(self): |
|
|
return self.self_improver.propose() |
|
|
|
|
|
def apply_improvement(self, proposal): |
|
|
return self.self_improver.evaluate_and_apply(proposal) |
|
|
|