#!/usr/bin/env python3 """ Self-Correcting Agent This module implements a simple self‑correcting agent that can solve arithmetic expressions and learn from user feedback. The agent keeps a knowledge base of previously solved problems and their correct answers. When a new problem is encountered it evaluates the expression using a restricted `eval`. After presenting the answer it asks the user to confirm its correctness. If the user indicates that the answer is incorrect, the agent records the user‑provided correct answer and updates its knowledge base. Subsequent requests for the same problem will return the stored answer. Author: Artur Kuzakhmetov License: MIT """ from __future__ import annotations import ast import operator import sys from pathlib import Path from typing import Dict, Tuple # Allowed operators for safe evaluation _ALLOWED_OPERATORS = { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, ast.Pow: operator.pow, ast.USub: operator.neg, ast.UAdd: operator.pos, } def _safe_eval(expr: str) -> float: """ Safely evaluate a simple arithmetic expression. Parameters ---------- expr : str The arithmetic expression to evaluate. Returns ------- float The numerical result of the expression. Raises ------ ValueError If the expression contains unsupported syntax or operators. """ try: node = ast.parse(expr, mode="eval") except SyntaxError as exc: raise ValueError(f"Invalid expression: {expr}") from exc def _eval(node: ast.AST) -> float: if isinstance(node, ast.Expression): return _eval(node.body) if isinstance(node, ast.Num): # Python <3.8 return node.n if isinstance(node, ast.Constant): # Python 3.8+ if isinstance(node.value, (int, float)): return node.value raise ValueError(f"Unsupported constant type: {type(node.value)}") if isinstance(node, ast.BinOp): left = _eval(node.left) right = _eval(node.right) op_type = type(node.op) if op_type in _ALLOWED_OPERATORS: return _ALLOWED_OPERATORS[op_type](left, right) raise ValueError(f"Unsupported operator: {op_type}") if isinstance(node, ast.UnaryOp): operand = _eval(node.operand) op_type = type(node.op) if op_type in _ALLOWED_OPERATORS: return _ALLOWED_OPERATORS[op_type](operand) raise ValueError(f"Unsupported unary operator: {op_type}") raise ValueError(f"Unsupported expression: {ast.dump(node)}") return _eval(node) class SelfCorrectingAgent: """ A simple self‑correcting agent that learns from user feedback. Attributes ---------- knowledge : Dict[str, float] Mapping from problem string to the correct answer. """ def __init__(self, knowledge_file: Path | None = None) -> None: self.knowledge: Dict[str, float] = {} self.knowledge_file = knowledge_file if knowledge_file and knowledge_file.exists(): self._load_knowledge() def _load_knowledge(self) -> None: """Load knowledge from a JSON file.""" import json with self.knowledge_file.open("r", encoding="utf-8") as f: data = json.load(f) self.knowledge = {k: float(v) for k, v in data.items()} def _save_knowledge(self) -> None: """Persist knowledge to a JSON file.""" if not self.knowledge_file: return import json with self.knowledge_file.open("w", encoding="utf-8") as f: json.dump(self.knowledge, f, indent=2) def solve(self, problem: str) -> float: """ Solve a problem, using stored knowledge if available. Parameters ---------- problem : str The arithmetic expression to solve. Returns ------- float The computed answer. """ if problem in self.knowledge: return self.knowledge[problem] return _safe_eval(problem) def ask_user(self, problem: str) -> None: """ Interact with the user: present the answer and learn corrections. Parameters ---------- problem : str The arithmetic expression to solve. """ try: answer = self.solve(problem) except ValueError as exc: print(f"Error: {exc}") return print(f"Answer: {answer}") while True: resp = input("Is this correct? (y/n): ").strip().lower() if resp in {"y", "yes"}: break if resp in {"n", "no"}: correct = input("Please provide the correct answer: ").strip() try: correct_val = float(correct) except ValueError: print("Invalid number. Try again.") continue self.knowledge[problem] = correct_val print("Knowledge updated.") break print("Please answer 'y' or 'n'.") def run(self) -> None: """ Run an interactive loop until the user exits. """ print("Self‑Correcting Agent") print("Type 'exit' to quit.") while True: problem = input("Enter problem: ").strip() if problem.lower() in {"exit", "quit"}: print("Goodbye!") self._save_knowledge() break if not problem: continue self.ask_user(problem) def main() -> None: """Entry point for the command‑line interface.""" agent = SelfCorrectingAgent(knowledge_file=Path("knowledge.json")) agent.run() if __name__ == "__main__": main()