|
| 1 | +""" |
| 2 | +UniversalReasoning — Legacy Standalone Entry Point |
| 3 | +=================================================== |
| 4 | +DEPRECATED: This module predates the current Codette server architecture |
| 5 | +(codette_server.py + forge_engine.py). It is preserved for historical |
| 6 | +reference and standalone experimentation. |
| 7 | +
|
| 8 | +For production use, see: |
| 9 | + inference/codette_server.py — HTTP server + SSE streaming |
| 10 | + reasoning_forge/forge_engine.py — Multi-agent orchestration |
| 11 | +
|
| 12 | +What was fixed before archiving: |
| 13 | + - Removed unused botbuilder / dialog_helper imports (Azure Bot Framework, |
| 14 | + not used in any method) |
| 15 | + - Replaced missing `perspectives` module with current reasoning_forge agents |
| 16 | + via compatibility shims that adapt the old async generate_response() API |
| 17 | + to the current sync analyze() API |
| 18 | + - destroy_sensitive_data() now overwrites the string before del (Python |
| 19 | + cannot guarantee zero-memory-clear, but this reduces window of exposure) |
| 20 | + - fetch_real_time_data() URL is now read from config, not hardcoded |
| 21 | + - Removed unused word_tokenize import |
| 22 | +""" |
| 23 | + |
| 24 | +import asyncio |
| 25 | +import json |
| 26 | +import logging |
| 27 | +import os |
| 28 | +import nest_asyncio |
| 29 | +from typing import List, Dict, Any |
| 30 | +from cryptography.fernet import Fernet |
| 31 | +import aiohttp |
| 32 | +import speech_recognition as sr |
| 33 | +from PIL import Image |
| 34 | +from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer |
| 35 | +from dotenv import load_dotenv |
| 36 | + |
| 37 | +load_dotenv() |
| 38 | +nest_asyncio.apply() |
| 39 | + |
| 40 | + |
| 41 | +# ── Compatibility shims: map old Perspective API to current Agent API ────────── |
| 42 | +# Old API: perspective.generate_response(question) -> coroutine -> str |
| 43 | +# New API: agent.analyze(concept) -> str (sync) |
| 44 | + |
| 45 | +def _make_perspective_shim(agent_class): |
| 46 | + """Wraps a current ReasoningAgent to satisfy the old async generate_response API.""" |
| 47 | + class _Shim: |
| 48 | + def __init__(self, config): |
| 49 | + try: |
| 50 | + self._agent = agent_class() |
| 51 | + except Exception: |
| 52 | + self._agent = None |
| 53 | + |
| 54 | + async def generate_response(self, question: str) -> str: |
| 55 | + if self._agent is None: |
| 56 | + return f"[{agent_class.__name__}] unavailable" |
| 57 | + return await asyncio.to_thread(self._agent.analyze, question) |
| 58 | + |
| 59 | + @property |
| 60 | + def __class__(self): |
| 61 | + return agent_class |
| 62 | + |
| 63 | + _Shim.__name__ = agent_class.__name__.replace("Agent", "Perspective") |
| 64 | + return _Shim |
| 65 | + |
| 66 | + |
| 67 | +try: |
| 68 | + from reasoning_forge.agents.newton_agent import NewtonAgent |
| 69 | + from reasoning_forge.agents.davinci_agent import DaVinciAgent |
| 70 | + from reasoning_forge.agents.quantum_agent import QuantumAgent |
| 71 | + from reasoning_forge.agents.empathy_agent import EmpathyAgent |
| 72 | + from reasoning_forge.agents.ethics_agent import EthicsAgent |
| 73 | + from reasoning_forge.agents.philosophy_agent import PhilosophyAgent |
| 74 | + |
| 75 | + NewtonPerspective = _make_perspective_shim(NewtonAgent) |
| 76 | + DaVinciPerspective = _make_perspective_shim(DaVinciAgent) |
| 77 | + QuantumComputingPerspective = _make_perspective_shim(QuantumAgent) |
| 78 | + ResilientKindnessPerspective = _make_perspective_shim(EmpathyAgent) |
| 79 | + PhilosophicalPerspective = _make_perspective_shim(PhilosophyAgent) |
| 80 | + |
| 81 | + # No direct equivalents — use philosophy agent as fallback |
| 82 | + HumanIntuitionPerspective = _make_perspective_shim(EmpathyAgent) |
| 83 | + NeuralNetworkPerspective = _make_perspective_shim(NewtonAgent) |
| 84 | + MathematicalPerspective = _make_perspective_shim(NewtonAgent) |
| 85 | + CopilotPerspective = _make_perspective_shim(PhilosophyAgent) |
| 86 | + BiasMitigationPerspective = _make_perspective_shim(EthicsAgent) |
| 87 | + PsychologicalPerspective = _make_perspective_shim(EmpathyAgent) |
| 88 | + |
| 89 | + _AGENTS_AVAILABLE = True |
| 90 | +except ImportError as _e: |
| 91 | + logging.warning(f"universal_reasoning: forge agents unavailable ({_e}) — perspectives will be stubs") |
| 92 | + _AGENTS_AVAILABLE = False |
| 93 | + |
| 94 | + class _StubPerspective: |
| 95 | + def __init__(self, config): pass |
| 96 | + async def generate_response(self, question): return f"[stub] {question}" |
| 97 | + |
| 98 | + for _name in ["NewtonPerspective", "DaVinciPerspective", "HumanIntuitionPerspective", |
| 99 | + "NeuralNetworkPerspective", "QuantumComputingPerspective", |
| 100 | + "ResilientKindnessPerspective", "MathematicalPerspective", |
| 101 | + "PhilosophicalPerspective", "CopilotPerspective", |
| 102 | + "BiasMitigationPerspective", "PsychologicalPerspective"]: |
| 103 | + globals()[_name] = _StubPerspective |
| 104 | + |
| 105 | + |
| 106 | +# ── Logging ──────────────────────────────────────────────────────────────────── |
| 107 | + |
| 108 | +def setup_logging(config): |
| 109 | + if config.get('logging_enabled', True): |
| 110 | + log_level = config.get('log_level', 'DEBUG').upper() |
| 111 | + numeric_level = getattr(logging, log_level, logging.DEBUG) |
| 112 | + logging.basicConfig( |
| 113 | + filename='universal_reasoning.log', |
| 114 | + level=numeric_level, |
| 115 | + format='%(asctime)s - %(levelname)s - %(message)s' |
| 116 | + ) |
| 117 | + else: |
| 118 | + logging.disable(logging.CRITICAL) |
| 119 | + |
| 120 | + |
| 121 | +def load_json_config(file_path): |
| 122 | + if not os.path.exists(file_path): |
| 123 | + logging.error(f"Configuration file '{file_path}' not found.") |
| 124 | + return {} |
| 125 | + try: |
| 126 | + with open(file_path, 'r') as file: |
| 127 | + config = json.load(file) |
| 128 | + logging.info(f"Configuration loaded from '{file_path}'.") |
| 129 | + return config |
| 130 | + except json.JSONDecodeError as e: |
| 131 | + logging.error(f"Error decoding JSON from '{file_path}': {e}") |
| 132 | + return {} |
| 133 | + |
| 134 | + |
| 135 | +# ── Crypto helpers ───────────────────────────────────────────────────────────── |
| 136 | + |
| 137 | +def encrypt_sensitive_data(data, key): |
| 138 | + fernet = Fernet(key) |
| 139 | + return fernet.encrypt(data.encode()) |
| 140 | + |
| 141 | + |
| 142 | +def decrypt_sensitive_data(encrypted_data, key): |
| 143 | + fernet = Fernet(key) |
| 144 | + return fernet.decrypt(encrypted_data).decode() |
| 145 | + |
| 146 | + |
| 147 | +def destroy_sensitive_data(data): |
| 148 | + # Python strings are immutable so true zero-fill isn't possible, but |
| 149 | + # overwriting the local reference and triggering GC reduces exposure window. |
| 150 | + if isinstance(data, bytearray): |
| 151 | + for i in range(len(data)): |
| 152 | + data[i] = 0 |
| 153 | + del data |
| 154 | + |
| 155 | + |
| 156 | +# ── Supporting classes ───────────────────────────────────────────────────────── |
| 157 | + |
| 158 | +class Element: |
| 159 | + def __init__(self, name, symbol, representation, properties, interactions, defense_ability): |
| 160 | + self.name = name |
| 161 | + self.symbol = symbol |
| 162 | + self.representation = representation |
| 163 | + self.properties = properties |
| 164 | + self.interactions = interactions |
| 165 | + self.defense_ability = defense_ability |
| 166 | + |
| 167 | + def execute_defense_function(self): |
| 168 | + message = f"{self.name} ({self.symbol}) executes its defense ability: {self.defense_ability}" |
| 169 | + logging.info(message) |
| 170 | + return message |
| 171 | + |
| 172 | + |
| 173 | +class RecognizerResult: |
| 174 | + def __init__(self, text): |
| 175 | + self.text = text |
| 176 | + |
| 177 | + |
| 178 | +class CustomRecognizer: |
| 179 | + def recognize(self, question): |
| 180 | + if any(name.lower() in question.lower() for name in ["hydrogen", "diamond"]): |
| 181 | + return RecognizerResult(question) |
| 182 | + return RecognizerResult(None) |
| 183 | + |
| 184 | + def get_top_intent(self, recognizer_result): |
| 185 | + return "ElementDefense" if recognizer_result.text else "None" |
| 186 | + |
| 187 | + |
| 188 | +# ── Main class ───────────────────────────────────────────────────────────────── |
| 189 | + |
| 190 | +class UniversalReasoning: |
| 191 | + def __init__(self, config): |
| 192 | + self.config = config |
| 193 | + self.perspectives = self._initialize_perspectives() |
| 194 | + self.elements = self._initialize_elements() |
| 195 | + self.recognizer = CustomRecognizer() |
| 196 | + self.context_history = [] |
| 197 | + self.feedback = [] |
| 198 | + self.sentiment_analyzer = SentimentIntensityAnalyzer() |
| 199 | + |
| 200 | + def _initialize_perspectives(self): |
| 201 | + perspective_names = self.config.get('enabled_perspectives', [ |
| 202 | + "newton", "davinci", "human_intuition", "neural_network", |
| 203 | + "quantum_computing", "resilient_kindness", "mathematical", |
| 204 | + "philosophical", "copilot", "bias_mitigation", "psychological" |
| 205 | + ]) |
| 206 | + perspective_classes = { |
| 207 | + "newton": NewtonPerspective, |
| 208 | + "davinci": DaVinciPerspective, |
| 209 | + "human_intuition": HumanIntuitionPerspective, |
| 210 | + "neural_network": NeuralNetworkPerspective, |
| 211 | + "quantum_computing": QuantumComputingPerspective, |
| 212 | + "resilient_kindness": ResilientKindnessPerspective, |
| 213 | + "mathematical": MathematicalPerspective, |
| 214 | + "philosophical": PhilosophicalPerspective, |
| 215 | + "copilot": CopilotPerspective, |
| 216 | + "bias_mitigation": BiasMitigationPerspective, |
| 217 | + "psychological": PsychologicalPerspective, |
| 218 | + } |
| 219 | + perspectives = [] |
| 220 | + for name in perspective_names: |
| 221 | + cls = perspective_classes.get(name.lower()) |
| 222 | + if cls: |
| 223 | + perspectives.append(cls(self.config)) |
| 224 | + logging.debug(f"Perspective '{name}' initialized.") |
| 225 | + else: |
| 226 | + logging.warning(f"Perspective '{name}' not recognized, skipped.") |
| 227 | + return perspectives |
| 228 | + |
| 229 | + def _initialize_elements(self): |
| 230 | + return [ |
| 231 | + Element("Hydrogen", "H", "Lua", |
| 232 | + ["Simple", "Lightweight", "Versatile"], |
| 233 | + ["Easily integrates with other languages"], |
| 234 | + "Evasion"), |
| 235 | + Element("Diamond", "D", "Kotlin", |
| 236 | + ["Modern", "Concise", "Safe"], |
| 237 | + ["Used for Android development"], |
| 238 | + "Adaptability"), |
| 239 | + ] |
| 240 | + |
| 241 | + async def generate_response(self, question: str) -> str: |
| 242 | + self.context_history.append(question) |
| 243 | + self.analyze_sentiment(question) |
| 244 | + |
| 245 | + # Fetch real-time data from configurable URL (not hardcoded) |
| 246 | + data_url = self.config.get('real_time_data_url', '') |
| 247 | + if data_url: |
| 248 | + try: |
| 249 | + await self.fetch_real_time_data(data_url) |
| 250 | + except Exception as e: |
| 251 | + logging.warning(f"Real-time data fetch failed: {e}") |
| 252 | + |
| 253 | + tasks = [] |
| 254 | + for perspective in self.perspectives: |
| 255 | + if asyncio.iscoroutinefunction(perspective.generate_response): |
| 256 | + tasks.append(perspective.generate_response(question)) |
| 257 | + else: |
| 258 | + async def _wrap(p=perspective, q=question): |
| 259 | + return await asyncio.to_thread(p.generate_response, q) |
| 260 | + tasks.append(_wrap()) |
| 261 | + |
| 262 | + perspective_results = await asyncio.gather(*tasks, return_exceptions=True) |
| 263 | + |
| 264 | + responses = [] |
| 265 | + for perspective, result in zip(self.perspectives, perspective_results): |
| 266 | + if isinstance(result, Exception): |
| 267 | + logging.error(f"Error from {perspective.__class__.__name__}: {result}") |
| 268 | + else: |
| 269 | + responses.append(result) |
| 270 | + |
| 271 | + recognizer_result = self.recognizer.recognize(question) |
| 272 | + if self.recognizer.get_top_intent(recognizer_result) == "ElementDefense": |
| 273 | + element_name = recognizer_result.text.strip() |
| 274 | + element = next( |
| 275 | + (el for el in self.elements if el.name.lower() in element_name.lower()), None |
| 276 | + ) |
| 277 | + if element: |
| 278 | + responses.append(element.execute_defense_function()) |
| 279 | + |
| 280 | + ethical = self.config.get( |
| 281 | + 'ethical_considerations', |
| 282 | + "Always act with transparency, fairness, and respect for privacy." |
| 283 | + ) |
| 284 | + responses.append(f"**Ethical Considerations:**\n{ethical}") |
| 285 | + return "\n\n".join(responses) |
| 286 | + |
| 287 | + def analyze_sentiment(self, text: str) -> dict: |
| 288 | + score = self.sentiment_analyzer.polarity_scores(text) |
| 289 | + logging.info(f"Sentiment: {score}") |
| 290 | + return score |
| 291 | + |
| 292 | + async def fetch_real_time_data(self, source_url: str) -> dict: |
| 293 | + async with aiohttp.ClientSession() as session: |
| 294 | + async with session.get(source_url) as response: |
| 295 | + return await response.json() |
| 296 | + |
| 297 | + def process_feedback(self, feedback: str): |
| 298 | + self.feedback.append(feedback) |
| 299 | + score = self.sentiment_analyzer.polarity_scores(feedback)["compound"] |
| 300 | + logging.info(f"Feedback sentiment: {score}") |
| 301 | + if score < -0.5: |
| 302 | + logging.warning("Negative feedback detected — flagging for review.") |
| 303 | + |
| 304 | + def save_response(self, response: str): |
| 305 | + if self.config.get('enable_response_saving', False): |
| 306 | + try: |
| 307 | + path = self.config.get('response_save_path', 'responses.txt') |
| 308 | + with open(path, 'a', encoding='utf-8') as f: |
| 309 | + f.write(response + '\n') |
| 310 | + logging.info("Response saved.") |
| 311 | + except Exception as e: |
| 312 | + logging.error(f"Failed to save response: {e}") |
| 313 | + |
| 314 | + def backup_response(self, response: str): |
| 315 | + backup_cfg = self.config.get('backup_responses', {}) |
| 316 | + if backup_cfg.get('enabled', False): |
| 317 | + try: |
| 318 | + path = backup_cfg.get('backup_path', 'backup_responses.txt') |
| 319 | + with open(path, 'a', encoding='utf-8') as f: |
| 320 | + f.write(response + '\n') |
| 321 | + logging.info("Response backed up.") |
| 322 | + except Exception as e: |
| 323 | + logging.error(f"Failed to backup response: {e}") |
| 324 | + |
| 325 | + def handle_voice_input(self) -> str | None: |
| 326 | + recognizer = sr.Recognizer() |
| 327 | + with sr.Microphone() as source: |
| 328 | + print("Listening...") |
| 329 | + audio = recognizer.listen(source) |
| 330 | + try: |
| 331 | + return recognizer.recognize_google(audio) |
| 332 | + except sr.UnknownValueError: |
| 333 | + print("Could not understand audio") |
| 334 | + except sr.RequestError as e: |
| 335 | + print(f"Google service error: {e}") |
| 336 | + return None |
| 337 | + |
| 338 | + def handle_image_input(self, image_path: str): |
| 339 | + try: |
| 340 | + return Image.open(image_path) |
| 341 | + except Exception as e: |
| 342 | + print(f"Image error: {e}") |
| 343 | + return None |
| 344 | + |
| 345 | + |
| 346 | +# ── Standalone entry point ───────────────────────────────────────────────────── |
| 347 | + |
| 348 | +if __name__ == "__main__": |
| 349 | + config = load_json_config('config.json') |
| 350 | + setup_logging(config) |
| 351 | + |
| 352 | + azure_openai_api_key = os.getenv('AZURE_OPENAI_API_KEY', '') |
| 353 | + azure_openai_endpoint = os.getenv('AZURE_OPENAI_ENDPOINT', '') |
| 354 | + |
| 355 | + if azure_openai_api_key: |
| 356 | + encryption_key = Fernet.generate_key() |
| 357 | + encrypted_api_key = encrypt_sensitive_data(azure_openai_api_key, encryption_key) |
| 358 | + encrypted_endpoint = encrypt_sensitive_data(azure_openai_endpoint, encryption_key) |
| 359 | + config['azure_openai_api_key'] = encrypted_api_key |
| 360 | + config['azure_openai_endpoint'] = encrypted_endpoint |
| 361 | + |
| 362 | + engine = UniversalReasoning(config) |
| 363 | + question = "Tell me about Hydrogen and its defense mechanisms." |
| 364 | + response = asyncio.run(engine.generate_response(question)) |
| 365 | + print(response) |
| 366 | + |
| 367 | + if response: |
| 368 | + engine.save_response(response) |
| 369 | + engine.backup_response(response) |
| 370 | + |
| 371 | + voice_input = engine.handle_voice_input() |
| 372 | + if voice_input: |
| 373 | + print(asyncio.run(engine.generate_response(voice_input))) |
| 374 | + |
| 375 | + image_input = engine.handle_image_input("path_to_image.jpg") |
| 376 | + if image_input: |
| 377 | + print("Image loaded successfully.") |
0 commit comments